Compare commits

...

660 Commits

Author SHA1 Message Date
Yoan.liu
dad1b4dfa6 update to version v0.3.2 2025-03-10 06:49:57 +08:00
Yoan.liu
786f2f8678 Merge pull request #498 from usual2970/hotfix/workflow
fix the issue where the deployment node could not set the certificate…
2025-03-09 12:42:22 +08:00
Yoan.liu
ed689dba41 restore currentlength 2025-03-09 12:40:32 +08:00
Yoan.liu
f779117ed6 fix the issue where the deployment node could not set the certificate source. 2025-03-09 12:23:14 +08:00
Fu Diwei
c9e7e00f42 update README 2025-03-09 11:03:32 +08:00
Yoan.liu
6019945d83 Merge branch 'main' of github.com:usual2970/certimate 2025-03-09 07:22:13 +08:00
Yoan.liu
e0aed060aa update to version 0.3.1 2025-03-09 07:21:56 +08:00
Yoan.liu
1b03be774d Merge pull request #494 from fudiwei/main
enhance & new providers
2025-03-09 07:14:19 +08:00
Fu Diwei
c7ad61e319 feat: add tencentcloud scf deployer 2025-03-08 14:58:40 +08:00
Fu Diwei
563a32ed62 fix: #495 2025-03-08 14:32:22 +08:00
Fu Diwei
1d4b88339e feat: add aliyun fc deployer 2025-03-08 14:30:01 +08:00
Fu Diwei
1e2e88e299 feat: allow insecure connections on deployment to some self-hosted services 2025-03-07 21:04:57 +08:00
Fu Diwei
29dda4ec66 feat: add 1panel deployer 2025-03-07 21:04:50 +08:00
Fu Diwei
6ccbdeb89a feat(ui): update default standard workflow template 2025-03-07 12:27:22 +08:00
Yoan.liu
5ae460c922 Merge pull request #488 from fudiwei/bugfix
serveral bugfix
2025-03-07 06:38:41 +08:00
Fu Diwei
48f3cc419b chore(ui): upgrade cron-parser 2025-03-07 00:18:39 +08:00
Fu Diwei
52e9341dab fix: inappropriate workflow node config unsaved reminder 2025-03-06 21:41:19 +08:00
Fu Diwei
411c39b148 fix: #485 2025-03-06 21:41:19 +08:00
Fu Diwei
574ad0445e refactor(ui): clean code 2025-03-06 21:41:16 +08:00
Fu Diwei
5b2bc6bff9 chore(ui): improve i18n 2025-03-06 18:28:43 +08:00
Fu Diwei
8a113e2bcb fix: missing parameter on deployment to tencentcloud ssl 2025-03-06 18:28:43 +08:00
Fu Diwei
9aaf3ff5d8 fix: #478 2025-03-06 18:28:43 +08:00
Fu Diwei
6d612f42a8 fix: #482 2025-03-06 18:28:43 +08:00
Fu Diwei
e2fdc29ca0 chore(deps): upgrade npm dependencies 2025-03-06 18:28:43 +08:00
Fu Diwei
b17dd04329 chore(deps): upgrade gomod dependencies 2025-03-06 18:28:31 +08:00
Fu Diwei
9a81d4a293 update README 2025-03-05 23:45:42 +08:00
Yoan.liu
5f971ea7e8 update version to v0.3.0 2025-03-05 21:17:58 +08:00
Yoan.liu
a68feda29c Merge pull request #475 from fudiwei/next
Reconfigure Github Actions for Docker Image CI
2025-03-03 21:48:24 +08:00
Fu Diwei
579c411900 build: config github actions workflow 2025-03-02 23:55:30 +08:00
RHQYZ
699f847d4a Merge branch 'usual2970:next' into next 2025-03-02 23:31:26 +08:00
RHQYZ
c370ac0609 chore: config editorconfig 2025-03-02 23:11:38 +08:00
RHQYZ
5db18ab749 update README 2025-03-02 00:31:04 +08:00
Fu Diwei
14ce139135 Merge branch 'next' of https://github.com/fudiwei/certimate into next 2025-03-02 00:29:16 +08:00
Fu Diwei
1745907bcb update README 2025-03-02 00:29:04 +08:00
RHQYZ
344c269f34 Merge pull request #469 from fudiwei/next
improve i18n
2025-03-01 01:44:08 +08:00
Fu Diwei
853feecfcc chore: improve i18n 2025-03-01 01:33:30 +08:00
Yoan.liu
28647d6902 Merge pull request #465 from fudiwei/hotfix
bugfix
2025-02-28 09:12:35 +08:00
Fu Diwei
c89633fcd5 fix: config or logger not be set in deployers 2025-02-26 12:51:17 +08:00
Fu Diwei
6e3d040127 feat: ptr util func 2025-02-25 18:41:23 +08:00
Fu Diwei
a2ac836629 feat: add azure keyvault uploader 2025-02-25 17:12:55 +08:00
Fu Diwei
3c91f29a91 fix: incorrect azure cloud environment 2025-02-24 20:37:38 +08:00
Fu Diwei
78600079a4 fix: incorrect content-type in baota sdk 2025-02-24 16:42:16 +08:00
Fu Diwei
429f8ec85e Merge branch 'next' into hotfix 2025-02-24 15:34:59 +08:00
Fu Diwei
8e5fce3e96 feat(ui): improve i18n 2025-02-24 15:32:53 +08:00
Yoan.liu
13238ae7b4 update version 2025-02-23 21:02:14 +08:00
Yoan.liu
569f94e885 Merge pull request #456 from fudiwei/feat/providers
feat: more providers
2025-02-22 14:47:22 +08:00
Yoan.liu
6e244d0657 Merge pull request #459 from fudiwei/bugfix/overlength-fields-error
bugfix #458
2025-02-22 14:46:29 +08:00
Fu Diwei
97356328be refactor: clean code 2025-02-21 17:28:58 +08:00
Fu Diwei
f81fa2eb63 feat: add dns.la dns-01 applicant 2025-02-21 17:21:39 +08:00
Fu Diwei
316a3c950f fix: nil poiner dereference 2025-02-21 14:48:04 +08:00
Fu Diwei
92528edbfb chore: set default jdcloud logger level to warning 2025-02-21 14:15:23 +08:00
Fu Diwei
32fdb3ed88 fix: typo 2025-02-21 13:53:44 +08:00
Fu Diwei
63f824e3dd feat: add namecheap dns-01 applicant 2025-02-21 10:32:30 +08:00
Fu Diwei
543d2d9d50 feat: add cmcccloud dns-01 applicant 2025-02-20 23:42:07 +08:00
Fu Diwei
6f94f4d882 refactor: reimpl custom lego dns providers 2025-02-20 22:20:18 +08:00
Fu Diwei
906141a415 feat: add jdcloud vod deployer 2025-02-20 22:05:58 +08:00
Fu Diwei
2cfaf4e231 feat: add tencentcloud vod deployer 2025-02-20 21:41:24 +08:00
Fu Diwei
54217217aa fix: insufficient certificate length 2025-02-20 21:06:04 +08:00
Fu Diwei
c492e2de28 feat: add aliyun vod deployer 2025-02-20 21:02:24 +08:00
Fu Diwei
73fec61409 fix: #458 2025-02-20 19:30:26 +08:00
Fu Diwei
ea70429889 feat: add jdcloud alb deployer 2025-02-20 17:39:15 +08:00
Fu Diwei
9febe47975 feat: add jdcloud ssl uploader 2025-02-20 14:32:42 +08:00
Fu Diwei
22d971db4b feat: add jdcloud live video deployer 2025-02-20 10:13:02 +08:00
Fu Diwei
5139198691 feat: add jdcloud cdn deployer 2025-02-20 00:51:34 +08:00
Fu Diwei
0e1f720419 refactor: normalize providers constructors 2025-02-20 00:16:26 +08:00
Fu Diwei
72896e052c feat: add jdcloud dns-01 applicant 2025-02-19 23:57:22 +08:00
Fu Diwei
469c24751e refactor: reimpl 3rd sdks 2025-02-19 21:55:38 +08:00
Fu Diwei
688a013d73 feat(ui): version checker 2025-02-18 21:21:29 +08:00
Fu Diwei
ff53866e9e refactor: normalize providers constructors 2025-02-18 19:19:00 +08:00
Fu Diwei
1bac6174ad feat: add baiducloud dns-01 applicant 2025-02-18 19:18:59 +08:00
Fu Diwei
c451bf5e03 feat: support multiple sites on deployment to baotapanel site 2025-02-18 19:18:59 +08:00
Fu Diwei
03d2f4ca32 feat: add cdnfly deployer 2025-02-18 19:18:56 +08:00
Fu Diwei
46f02331fd feat: add cachefly deployer 2025-02-18 15:16:24 +08:00
Fu Diwei
7c3f2399c2 feat: add gcore cdn deployer 2025-02-18 15:16:20 +08:00
Fu Diwei
ea02190ad5 feat: add gcore uploader 2025-02-18 15:16:19 +08:00
Fu Diwei
e2a148c25f feat: add gcore dns-01 applicant 2025-02-18 15:16:19 +08:00
Fu Diwei
b2eb5d2754 feat: add baishan cdn deployer 2025-02-18 15:16:19 +08:00
Fu Diwei
c72dc0d2c4 feat: add safeline deployer 2025-02-18 15:16:19 +08:00
Fu Diwei
a40b078e9c feat: add tencentcloud waf deployer 2025-02-18 15:16:19 +08:00
Fu Diwei
61b7165bac feat: add huaweicloud waf deployer 2025-02-18 15:16:19 +08:00
Fu Diwei
a6f1f21c18 feat: add huaweicloud waf uploader 2025-02-18 15:16:19 +08:00
Fu Diwei
b734ffcf9d feat: add baotapanel console deployer 2025-02-18 15:16:03 +08:00
Fu Diwei
6d8301b159 Merge branch 'feat/new-workflow' into feat/providers 2025-02-15 11:11:06 +08:00
Yoan.liu
66bdd923d7 Merge pull request #453 from fudiwei/feat/new-workflow
feat: enhance
2025-02-15 09:30:11 +08:00
Fu Diwei
879da92419 feat: add volcengine imagex deployer 2025-02-14 21:01:33 +08:00
Fu Diwei
b9356a5653 feat(ui): new deploy provider category website 2025-02-14 16:34:52 +08:00
Fu Diwei
d21c027db8 refactor: drop access field usage 2025-02-14 16:20:53 +08:00
Fu Diwei
fe93334f86 chore: create migration snapshot 2025-02-14 00:27:25 +08:00
Fu Diwei
d588e14a58 feat: reserved providers 2025-02-13 22:20:56 +08:00
Fu Diwei
0b7b544d4e feat: deploy provider category 2025-02-13 21:50:04 +08:00
Fu Diwei
664bb692b6 feat: search by keyword on AccessList, CertificateList, WorkflowList 2025-02-13 21:50:04 +08:00
Fu Diwei
970fba90e0 chore: rename 2025-02-13 21:50:00 +08:00
Fu Diwei
f6c338b50e chore(deps): upgrade npm denpendencies 2025-02-13 21:50:00 +08:00
Fu Diwei
3bc708b910 chore(deps): upgrade gomod denpendencies 2025-02-13 21:49:54 +08:00
Yoan.liu
041325d67e update version 2025-02-12 09:55:44 +08:00
Yoan.liu
51600e7ad0 Merge branch 'fudiwei-feat/providers' into next 2025-02-12 09:53:17 +08:00
Yoan.liu
4a0e3c9a69 fix conflict 2025-02-12 09:53:09 +08:00
Yoan.liu
41ff0241af Merge branch 'next' of github.com:usual2970/certimate into next 2025-02-12 09:48:42 +08:00
Yoan.liu
12d62bde98 Merge pull request #443 from fudiwei/feat/new-workflow
feat: enhance & bugfix
2025-02-12 09:48:06 +08:00
Yoan.liu
138e08e985 Merge branch 'feat/new-workflow' of github.com:fudiwei/certimate into next 2025-02-12 09:42:00 +08:00
Fu Diwei
94408e8a9f refactor: clean code 2025-02-11 19:31:24 +08:00
Fu Diwei
f3c7d096bc fix(ui): missing css 2025-02-11 19:17:38 +08:00
Fu Diwei
774ed5d31e feat: reserved field challengeType for apply node config 2025-02-11 19:03:58 +08:00
Fu Diwei
b07174b533 feat: cascade delete related runs and outputs when delete workflow 2025-02-11 16:45:51 +08:00
Fu Diwei
45de2cf1db edit README 2025-02-11 00:02:43 +08:00
Fu Diwei
81fe230be4 feat: add baota panel deployer 2025-02-11 00:02:40 +08:00
Fu Diwei
6673871db2 feat: add tencent cloud ssl-deploy deployer 2025-02-10 22:34:01 +08:00
Fu Diwei
316bd58b68 feat: add aliyun cas-deploy deployer 2025-02-10 22:33:41 +08:00
Fu Diwei
ac4c375243 feat: add aliyun esa deployer 2025-02-10 17:59:36 +08:00
Fu Diwei
5da142ab83 fix: memory leak 2025-02-10 16:27:01 +08:00
Fu Diwei
cbf711ee60 feat: save run logs when each workflow node completed 2025-02-10 16:19:04 +08:00
Fu Diwei
4f5c1dc6d7 refactor: new workflow run logs 2025-02-10 13:07:45 +08:00
Fu Diwei
75c89b3d0b feat(ui): display artifact certificates in WorkflowRunDetail 2025-02-10 13:07:45 +08:00
Fu Diwei
b8513eb0b6 fix: different cronexpr rules between ui and pocketbase 2025-02-10 13:07:41 +08:00
Fu Diwei
a74ec95a6a feat(ui): subscribe workflow runs status 2025-02-08 23:08:25 +08:00
Fu Diwei
0bc40fd676 feat: workflow run dispatcher 2025-02-08 23:08:21 +08:00
Fu Diwei
b9e28db089 fix: nil pointer dereference 2025-02-08 23:08:14 +08:00
Yoan.liu
1f6b33f4f6 update version 2025-02-08 09:00:15 +08:00
Yoan.liu
049707acdc Merge pull request #438 from hujingnb/fix/k8s_secret
fix: k8s secret not updated
2025-02-08 08:56:52 +08:00
Fu Diwei
886f166e66 refactor: clean code 2025-02-07 09:19:17 +08:00
Fu Diwei
3f9fda8a2d feat: support multiple workflow outputs 2025-02-06 23:37:44 +08:00
Fu Diwei
d32fce98ae feat: save related runId in certificates or workflow outputs 2025-02-06 23:37:44 +08:00
Fu Diwei
5b9e39a449 fix: #439 2025-02-06 23:37:44 +08:00
Fu Diwei
4b931f782e refactor(ui): clean code 2025-02-06 23:37:44 +08:00
Fu Diwei
24b591ed62 fix: nil pointer dereference 2025-02-06 23:37:44 +08:00
Fu Diwei
a41ee9c3ca feat: enhance certificate model 2025-02-06 23:37:44 +08:00
Fu Diwei
5f5c835533 feat: add ExtractCertificatesFromPEM util func 2025-02-06 23:37:44 +08:00
Fu Diwei
bc29cce645 chore(deps): upgrade gomod dependencies 2025-02-06 23:37:44 +08:00
Fu Diwei
98f4f1cc99 fix: conflict pocketbase superuser initializations 2025-02-06 23:37:44 +08:00
Fu Diwei
d11fc1c07e refactor: reimpl qiniu sdk 2025-02-06 23:37:38 +08:00
hujing
e019bfe136 fix: k8s secret not updated 2025-01-31 00:50:40 +08:00
Yoan.liu
57f8db010b Merge pull request #433 from fudiwei/feat/new-workflow
feat: more providers
2025-01-24 10:26:30 +08:00
Fu Diwei
0e1a964e7c feat: add gname applicant 2025-01-24 03:42:34 +08:00
Fu Diwei
469d4b35c1 feat: implement gname api sdk 2025-01-24 01:38:06 +08:00
Fu Diwei
a78a815ccc fix: typo 2025-01-23 23:54:45 +08:00
Fu Diwei
9f7cffce21 feat: allow fallback to use scp on deployment to ssh 2025-01-23 23:50:11 +08:00
Fu Diwei
5ee5460612 feat: add aws cloudfront deployer 2025-01-23 23:50:07 +08:00
Fu Diwei
1651cda5b4 feat: add aws acm uploader 2025-01-23 23:49:56 +08:00
Fu Diwei
9370f9d68f feat: add cloudns applicant 2025-01-23 23:49:56 +08:00
Fu Diwei
2a7be1b24d feat: add aliyun waf deployer 2025-01-23 23:49:56 +08:00
Fu Diwei
2965fb2b47 feat: add rainyun applicant 2025-01-23 23:49:56 +08:00
Fu Diwei
6c3c29dd11 feat: add westcn applicant 2025-01-23 23:49:56 +08:00
Fu Diwei
adb43dfee1 feat: add qiniu pili deployer 2025-01-23 23:49:49 +08:00
Yoan.liu
c0386b153e Merge pull request #430 from fudiwei/feat/new-workflow
feat: enhance workflow
2025-01-23 09:37:43 +08:00
Fu Diwei
5cabceb08e feat(ui): improve workflow elements scroll area 2025-01-23 03:02:59 +08:00
Fu Diwei
b67049f9aa refactor: clean code 2025-01-22 22:11:04 +08:00
Fu Diwei
7a2fc5e2fd Merge branch 'next' into feat/new-workflow 2025-01-22 20:21:32 +08:00
Yoan.liu
5f213b5f51 Adjustable scaling ratio 2025-01-22 10:52:46 +08:00
Yoan.liu
97c73aae16 Merge pull request #428 from usual2970/feat/upload_certificate
add upload certificate node
2025-01-22 10:03:55 +08:00
Yoan.liu
101d77e4ae parse privatekey using certcrypto 2025-01-22 10:03:13 +08:00
Fu Diwei
0f945881a1 feat: cancel workflow run 2025-01-22 04:13:16 +08:00
Fu Diwei
bee4ba10cb feat: generate run record at the beginning of the workflow execution 2025-01-22 03:48:58 +08:00
Fu Diwei
7e0f575e0a feat(ui): jump to workflow detail page in dashboard 2025-01-22 03:44:54 +08:00
Fu Diwei
79c1da6d14 feat: a new status for canceled workflow run 2025-01-22 03:13:31 +08:00
Fu Diwei
8dc86209df feat: support removing workflow runs 2025-01-21 23:11:48 +08:00
Fu Diwei
c61b2d2d3f fix: couldn't list expire soon certificates 2025-01-21 21:41:25 +08:00
Yoan.liu
c1f2437998 Create FUNDING.yml 2025-01-21 10:42:52 +08:00
Yoan.liu
1039591677 add upload certificate node 2025-01-21 08:02:46 +08:00
Fu Diwei
d5568608f5 refactor: clean code 2025-01-21 00:42:28 +08:00
Yoan.liu
6bdcfaaef0 Merge pull request #425 from usual2970/feat/result_branch
添加执行结果分支节点
2025-01-20 09:46:50 +08:00
Yoan.liu
101d55bafa Merge pull request #426 from fudiwei/feat/new-workflow
feat: support ARI
2025-01-20 09:46:32 +08:00
Fu Diwei
fa8ba061fb feat: support ARI 2025-01-20 02:28:40 +08:00
yoan
1b362673c0 fix conflict 2025-01-19 19:02:58 +08:00
Yoan.liu
11d654e902 Merge pull request #424 from fudiwei/feat/new-workflow
feat: enhance & bugfix
2025-01-19 18:54:34 +08:00
yoan
e6e964aa8c add execute result branch 2025-01-19 17:01:02 +08:00
Fu Diwei
c0dc9b1882 feat(ui): improve workflow runs history 2025-01-19 06:15:38 +08:00
Fu Diwei
5b613bcf84 feat: support configuring repeatable deploy in deployment 2025-01-19 06:02:49 +08:00
Fu Diwei
c71d14cafa feat: support configuring renewal interval in application 2025-01-19 05:37:28 +08:00
Fu Diwei
60a13aaf17 feat: support configuring dns ttl in application 2025-01-19 05:01:36 +08:00
Fu Diwei
c1f77dd92f refactor: clean code 2025-01-19 03:34:38 +08:00
Fu Diwei
ce4c590b1c refactor: clean code 2025-01-18 22:25:20 +08:00
Fu Diwei
3e1ecd60a1 chore(deps): upgrade npm dependencies 2025-01-18 18:37:04 +08:00
Fu Diwei
2171faa330 fix(ui): modal form input focus problem 2025-01-18 18:37:01 +08:00
Fu Diwei
d5e4ea385d feat: download certificate archive 2025-01-18 07:09:41 +08:00
Fu Diwei
d28b89f03e fix: couldn't trasform ecc certificate to pfx format 2025-01-18 07:09:41 +08:00
Fu Diwei
6adcc61447 refactor: clean code 2025-01-18 07:09:41 +08:00
Fu Diwei
ecde12ec23 feat(ui): improve ssl providers switch warning 2025-01-18 07:09:41 +08:00
Fu Diwei
c66027ae8a build: rebuilld pocketbase migration snapshot 2025-01-18 07:09:41 +08:00
Fu Diwei
32f9c95dd0 feat: migrate pocketbase to v0.23 2025-01-18 07:09:41 +08:00
Fu Diwei
1568e5a2a7 fix: incorrect config in deployment on aliyun oss 2025-01-18 07:09:27 +08:00
Fu Diwei
e4a534cb7c Merge branch 'next' into feat/new-workflow 2025-01-17 19:47:44 +08:00
Fu Diwei
ee2cca17fe Merge branch 'next' into feat/new-workflow 2025-01-17 18:07:50 +08:00
Fu Diwei
0869eaafdd refactor: clean code 2025-01-17 18:01:47 +08:00
yoan
69d4b3f93d fix dockerfile 2025-01-17 16:54:37 +08:00
yoan
61b37f38e2 update version 2025-01-17 16:22:11 +08:00
Yoan.liu
c69c560de0 Merge pull request #420 from usual2970/feat/async_apply
池化申请证书请求
2025-01-17 16:20:15 +08:00
Yoan.liu
0d3e426dff Merge pull request #421 from fudiwei/feat/new-workflow
feat: enhance workflow
2025-01-17 11:01:30 +08:00
yoan
4fe68d3b9f limit request rate 2025-01-17 10:56:09 +08:00
Fu Diwei
dab6ad917c refactor: remove unused code 2025-01-16 23:42:53 +08:00
Fu Diwei
a20b82b9cf feat: re-run workflow nodes when critical configurations changed 2025-01-16 23:02:08 +08:00
Fu Diwei
087fd81879 feat: support configuring pb-data-dir on app launch 2025-01-16 22:23:00 +08:00
Fu Diwei
d1dbbae101 feat(ui): show errmsg if table loaded error 2025-01-16 22:07:01 +08:00
Fu Diwei
3a2baba746 feat: support removing certificates 2025-01-16 21:53:51 +08:00
Fu Diwei
831f0ee5d9 feat(ui): improve responsive ui 2025-01-16 21:50:16 +08:00
Fu Diwei
e10fb64d6b Merge branch 'feat/new-workflow' of https://github.com/fudiwei/certimate into feat/new-workflow 2025-01-16 20:30:01 +08:00
Fu Diwei
8ecb71fb55 refactor: clean code 2025-01-16 20:29:28 +08:00
Fu Diwei
dea4106569 fix: couldn't return stdout or stderr during script execution if errors occur on deployment to local/ssh 2025-01-16 20:29:24 +08:00
yoan
2dd8fb2ee2 pool certificate issuance requests 2025-01-16 14:42:54 +08:00
Yoan.liu
2218be5d34 Merge pull request #419 from fudiwei/feat/new-workflow
feat: more providers
2025-01-16 11:23:47 +08:00
Fu Diwei
d712f07b96 refactor: reimplement webhook deployer 2025-01-15 23:04:43 +08:00
Fu Diwei
b657405e46 refactor: clean code 2025-01-15 22:45:34 +08:00
Fu Diwei
974c320925 feat: add edgio applications v7 deployer 2025-01-15 22:45:29 +08:00
Fu Diwei
dd236b925d feat: add ns1 applicant 2025-01-15 14:24:51 +08:00
Fu Diwei
e264d71048 refactor: slices utils 2025-01-15 14:24:48 +08:00
Yoan.liu
23f83b9377 Merge pull request #416 from fudiwei/feat/new-workflow
feat: more providers
2025-01-15 09:11:58 +08:00
Fu Diwei
db68721834 feat: add ucloud us3 deployer 2025-01-14 22:19:40 +08:00
Fu Diwei
6a9cf2ed28 feat(ui): improve responsive ui 2025-01-14 21:46:09 +08:00
Fu Diwei
3dd79d447b update README 2025-01-14 21:34:22 +08:00
Fu Diwei
e87ac72281 feat: add ucloud ucdn deployer 2025-01-14 21:31:10 +08:00
Fu Diwei
e430109228 feat: add ucloud ussl uploader 2025-01-14 21:02:08 +08:00
yoan
e7e123af0d Merge branch 'next' of github.com:usual2970/certimate into next 2025-01-14 12:09:55 +08:00
yoan
7b75dacb03 clean up unnecessary lines 2025-01-14 12:09:12 +08:00
Yoan.liu
493f2a508b Merge pull request #415 from fudiwei/feat/new-workflow
feat: more providers
2025-01-14 08:18:03 +08:00
Fu Diwei
70b8aaf845 update README 2025-01-13 22:26:21 +08:00
Fu Diwei
ab1c9bfdbc feat: add tencentcloud css deployer 2025-01-13 21:47:38 +08:00
Fu Diwei
643d820965 feat: add aliyun live deployer 2025-01-13 21:47:34 +08:00
Fu Diwei
8aa5c3ca65 refactor: clean code 2025-01-13 20:04:46 +08:00
Fu Diwei
7160589ac7 refactor: clean code 2025-01-13 20:03:07 +08:00
Fu Diwei
21cc1d43de feat: support sni domain on deployment to aliyun clb & alb 2025-01-13 20:02:57 +08:00
yoan
793289ad97 mod tidy 2025-01-13 15:27:15 +08:00
Yoan.liu
bea2f00a90 Merge pull request #414 from fudiwei/feat/new-workflow
feat: more providers
2025-01-13 15:12:17 +08:00
yoan
d08da18b4a update preview link 2025-01-13 11:38:20 +08:00
yoan
1e03fcff67 update readme 2025-01-13 11:12:07 +08:00
yoan
ffb516d343 update vedio 2025-01-13 10:53:40 +08:00
yoan
45f9913bdb update readme 2025-01-13 10:43:29 +08:00
Fu Diwei
a6e9cc03c0 Merge branch 'next' into feat/new-workflow 2025-01-12 21:27:35 +08:00
Fu Diwei
b5db2d565a feat: add azure dns applicant 2025-01-12 21:26:31 +08:00
Fu Diwei
e5518b1067 feat: add volcengine clb deployer 2025-01-12 21:26:31 +08:00
Fu Diwei
a5c9ed8d17 feat: add volcengine tos deployer 2025-01-12 21:26:31 +08:00
Fu Diwei
b5094a3cc9 feat: add volcengine dcdn deployer 2025-01-12 21:26:31 +08:00
Fu Diwei
99c5c8339d feat: add volcengine cert-center uploader 2025-01-12 21:26:19 +08:00
Yoan.liu
9f7e0f8a26 Merge pull request #413 from usual2970/feat/dashboard
refine the dashboard
2025-01-12 20:45:42 +08:00
yoan
75bcbe52fd Merge branch 'fudiwei-feat/new-workflow-ui' into next 2025-01-12 20:45:07 +08:00
yoan
8c4a239631 fix build error 2025-01-12 20:44:51 +08:00
yoan
503d9c34f8 refine the dashboard 2025-01-12 19:55:40 +08:00
Fu Diwei
d9f38c38a6 feat(ui): add prompt message during workflow running 2025-01-11 16:51:21 +08:00
Fu Diwei
598d0705fb feat: extract some configs from access to apply logic 2025-01-11 16:31:49 +08:00
Fu Diwei
a0c08e841d feat: separate access providers and dns providers 2025-01-11 16:31:44 +08:00
Fu Diwei
8ed2b2475c refactor: clean code 2025-01-10 21:22:22 +08:00
Fu Diwei
e4e0a24a06 Merge branch 'next' into feat/new-workflow-ui 2025-01-10 12:15:19 +08:00
Yoan.liu
9839e2bb60 Merge pull request #410 from usual2970/feat/async
异步执行workflow
2025-01-10 07:25:52 +08:00
yoan
db10ed8378 handle exit logic 2025-01-10 07:25:09 +08:00
yoan
ebffac7ba4 execute workflows asynchronously 2025-01-09 20:00:15 +08:00
Fu Diwei
f99dd4f89a test: improve example 2025-01-08 20:18:43 +08:00
RHQYZ
6badc0f419 fix: build error 2025-01-08 18:32:50 +08:00
Yoan.liu
aa1b69d7f2 Merge pull request #407 from fudiwei/feat/new-workflow-ui
bugfix #405 on v0.3.x
2025-01-08 18:10:20 +08:00
Fu Diwei
eb3fec1ac0 fix: incorrect nil check logic in tencentcloud cdn and ecdn deployment 2025-01-08 16:05:02 +08:00
Fu Diwei
0f772d55ab refactor(ui): clean code 2025-01-08 15:53:17 +08:00
Yoan.liu
9cea6775d1 Merge pull request #390 from fudiwei/feat/new-workflow-ui
feat: new UI
2025-01-08 09:37:37 +08:00
Fu Diwei
7e376071f5 fix: nil pointer 2025-01-07 01:10:26 +08:00
Fu Diwei
9a937fa072 feat(ui): shared workflow node dropdown menu 2025-01-07 00:57:10 +08:00
Fu Diwei
84c36a4eec feat: improve workflow node configuration 2025-01-06 23:46:14 +08:00
Fu Diwei
155371cdd0 feat: letsencrypt staging environment 2025-01-06 20:05:06 +08:00
Fu Diwei
87e1749553 fix(ui): antd nested form bugs 2025-01-06 19:10:29 +08:00
Fu Diwei
4ba7237326 feat(ui): close confirm when changes not saved 2025-01-06 00:44:06 +08:00
Fu Diwei
6f1a375fee refactor: clean code 2025-01-05 22:59:00 +08:00
Fu Diwei
350160833b feat(ui): new workflow node panel 2025-01-05 22:53:47 +08:00
Fu Diwei
e4c51aece4 refactor: clean code 2025-01-05 22:53:40 +08:00
Fu Diwei
dfc192cb68 refactor: clean code 2025-01-05 16:34:15 +08:00
Fu Diwei
2a68372713 refactor: clean code 2025-01-05 04:08:34 +08:00
Fu Diwei
8af5235e4d refactor: clean code 2025-01-05 03:34:46 +08:00
Fu Diwei
7cf96d7d7e feat: release and discard workflow changes 2025-01-05 02:38:01 +08:00
Fu Diwei
9c4831fa3f fix: couldn't skip certificate not found error 2025-01-05 01:39:47 +08:00
Fu Diwei
8cf1ffd38b fix: couldn't get certificate effect time or expire time 2025-01-05 01:27:21 +08:00
Fu Diwei
3c70a4f455 fix: couldn't save certificate source 2025-01-05 01:16:00 +08:00
Fu Diwei
ddb6a88392 fix(ui): wrong form initial values 2025-01-05 00:47:27 +08:00
Fu Diwei
61843a4997 refactor: clean code 2025-01-05 00:08:12 +08:00
Fu Diwei
3b9a7fe805 feat: workflow run status & time 2025-01-04 22:07:01 +08:00
Fu Diwei
b686579acc feat: rename workflow_run_log to workflow_run 2025-01-04 16:53:58 +08:00
Fu Diwei
01ede08a79 feat: rename input to inputs, output to outputs 2025-01-04 16:41:30 +08:00
Fu Diwei
ae11d5ee3d feat: rename san to subjectAltNames, workflow to workflowId, nodeId to workflowNodeId, output to workflowOutputId, log to logs, succeed to succeeded 2025-01-04 16:29:14 +08:00
Fu Diwei
9246878d0e feat: rename domain to subjectAltNames 2025-01-04 14:04:47 +08:00
Fu Diwei
5387c373e0 feat: rename email to contactEmail 2025-01-04 13:39:08 +08:00
Fu Diwei
da76d1065e feat: rename , executionMethod/type to trigger, crontab to triggerCron 2025-01-04 13:29:03 +08:00
Fu Diwei
2213399f5e feat(ui): disable nodes during workflow running 2025-01-04 12:58:45 +08:00
Fu Diwei
52dfa5e8c3 feat: rename access to providerAccessId 2025-01-04 12:37:34 +08:00
Fu Diwei
90058b2dae feat: support template variables in webhook deployment 2025-01-04 10:26:57 +08:00
Fu Diwei
e695c8ee5c feat: rename configType/providerType to provider 2025-01-03 22:20:34 +08:00
Fu Diwei
849e065bb2 refactor(ui): clean code 2025-01-03 21:58:05 +08:00
Fu Diwei
b7cd07c996 fix(ui): workflow branch edge 2025-01-03 21:35:12 +08:00
Fu Diwei
52ee3863ae feat(ui): enhance WorkflowNew 2025-01-03 21:31:17 +08:00
Fu Diwei
5ce5a08e41 style(ui): eslint-plugin-tailwindcss 2025-01-03 20:35:11 +08:00
Fu Diwei
8a16893082 feat(ui): new WorkflowElements using antd 2025-01-03 20:29:34 +08:00
Fu Diwei
c6a8f923e4 feat(ui): WorkflowNew page 2025-01-02 20:24:16 +08:00
Fu Diwei
b6dd2248c8 style(ui): eslint-sort-imports 2025-01-02 12:50:38 +08:00
Fu Diwei
1588179bc9 refactor(ui): clean code 2025-01-02 10:28:18 +08:00
Fu Diwei
7f36621882 refactor(ui): clean code 2025-01-02 10:11:35 +08:00
Fu Diwei
e256d36cd1 refactor(ui): clean code 2025-01-02 10:04:23 +08:00
Fu Diwei
b2417ad902 fix(ui): useEffect deps 2025-01-01 20:52:36 +08:00
Fu Diwei
67a32a98a9 fix(ui): date format 2025-01-01 20:52:18 +08:00
Fu Diwei
78d9d5159a style: eslint-plugin-import 2025-01-01 20:40:59 +08:00
Fu Diwei
e2d29b8fa2 feat: configure k8s secret type 2025-01-01 19:13:48 +08:00
Fu Diwei
880c8819b4 chore(deps): upgrade npm dependencies 2025-01-01 19:12:54 +08:00
Fu Diwei
6075cc5c95 feat(ui): release & run workflow 2025-01-01 17:22:19 +08:00
Fu Diwei
5c1854948c feat(ui): improve i18n 2025-01-01 14:22:23 +08:00
Fu Diwei
7bd0cbce10 feat(ui): improve i18n 2025-01-01 14:04:41 +08:00
Fu Diwei
9c645a1efa chore: remove unused code 2024-12-31 20:05:48 +08:00
Fu Diwei
6f088fd76a feat(ui): new DeployNodeForm using antd 2024-12-31 19:55:34 +08:00
Fu Diwei
cb7a465d6c refactor: clean code 2024-12-28 16:59:36 +08:00
Fu Diwei
416f5e0986 refactor: clean code 2024-12-28 16:26:01 +08:00
Fu Diwei
86133ba52b refactor: clean code 2024-12-27 19:35:50 +08:00
Fu Diwei
047479426a refactor(ui): clean code 2024-12-27 17:02:21 +08:00
Fu Diwei
fb2d292cbf feat(ui): multiple input domains & nameservers in ApplyNodeForm 2024-12-27 16:42:07 +08:00
Fu Diwei
75cf552e72 refactor(ui): useTriggerElement 2024-12-27 12:47:45 +08:00
Fu Diwei
77537e7005 refactor: rename Timeout to PropagationTimeout during ACME DNS-01 authentication 2024-12-27 09:50:54 +08:00
Fu Diwei
dae6ad2951 feat(ui): add @ant-design/icons 2024-12-26 13:02:22 +08:00
Fu Diwei
8a816ba44f feat(ui): new WorkflowApplyNodeForm using antd 2024-12-26 03:06:15 +08:00
Fu Diwei
a9d918aa95 refactor(ui): validators util 2024-12-26 01:07:47 +08:00
Fu Diwei
a980904e38 chore: remove unused code 2024-12-26 00:42:23 +08:00
Fu Diwei
4008c2bfd5 refactor(ui): clean code 2024-12-25 23:28:42 +08:00
Fu Diwei
1184e52ba9 refactor(ui): clean code 2024-12-25 23:20:09 +08:00
Fu Diwei
adbf40914e chore: remove unused code 2024-12-25 21:26:32 +08:00
Fu Diwei
9b9083dfa1 Merge branch 'next' into feat/new-workflow-ui 2024-12-25 21:06:59 +08:00
Fu Diwei
6bd3b4998e feat(ui): new WorkflowNotifyNodeForm using antd 2024-12-25 20:57:09 +08:00
Yoan.liu
1f602c00be Merge pull request #394 from RangerCD/feat-name-dot-com
feat(provider): add name.com
2024-12-25 15:35:20 +08:00
Fu Diwei
4d0f7c2e02 refactor(ui): useAntdForm 2024-12-25 14:51:32 +08:00
Fu Diwei
c9024c5611 feat(ui): new WorkflowStartNodeForm using antd 2024-12-25 00:36:02 +08:00
RangerCD
a92dc2bbe6 fix(provider): typo while adding name.com 2024-12-24 22:45:39 +08:00
Fu Diwei
401fa3dcdd feat(ui): new WorkflowRuns using antd 2024-12-24 22:00:17 +08:00
Fu Diwei
4e5373de73 feat(ui): new WorkflowDetail using antd 2024-12-24 20:39:01 +08:00
RHQYZ
956fbb7833 feat(ui): improve i18n 2024-12-24 19:29:15 +08:00
RangerCD
6217d3aacd feat(provider): add name.com 2024-12-24 19:02:09 +08:00
Fu Diwei
8b1ae309fb refactor(ui): useZustandShallowSelector 2024-12-24 15:07:39 +08:00
Fu Diwei
52d24ff2f2 feat: improve i18n 2024-12-23 22:46:07 +08:00
Fu Diwei
7a66bdf139 fix: fix typo 2024-12-23 22:46:01 +08:00
Fu Diwei
16bc12c15b feat update placeholder syntax in notify templates 2024-12-23 22:33:12 +08:00
Fu Diwei
0556d68a4e feat(ui): MultipleInput 2024-12-23 22:22:00 +08:00
Fu Diwei
586c7fa927 feat: create DNSProvider using independent config instead of envvar 2024-12-23 19:58:51 +08:00
Fu Diwei
9ef16ebcf9 refactor: clean code 2024-12-23 19:31:48 +08:00
Fu Diwei
d509445519 refactor: clean code 2024-12-23 15:31:41 +08:00
Fu Diwei
d7bff599b7 chore(deps): upgrade gomod dependencies 2024-12-23 15:05:25 +08:00
Fu Diwei
cda54085b9 chore(deps): upgrade npm dependencies 2024-12-23 13:36:18 +08:00
Fu Diwei
984aae1ca6 chore: remove unused code 2024-12-22 20:10:04 +08:00
Fu Diwei
695c99119f Merge branch 'next' into feat/new-workflow-ui 2024-12-22 19:48:34 +08:00
Fu Diwei
d7e205aee7 feat(ui): improve i18n 2024-12-22 19:45:01 +08:00
Fu Diwei
09919cb3cb fix(ui): couldn't save ssh key 2024-12-22 19:35:05 +08:00
yoan
ba73e04046 fix build error 2024-12-22 18:43:18 +08:00
yoan
88cbf30fde update version 2024-12-22 18:38:24 +08:00
yoan
ed37add29f Merge branch 'fudiwei-feat/new-workflow-ui' into next 2024-12-22 18:37:52 +08:00
yoan
6d25f9c205 fix build error 2024-12-22 18:37:03 +08:00
Fu Diwei
01d30bb742 feat: add wecom notifier 2024-12-22 11:25:08 +08:00
Fu Diwei
a1fec5f6ac chore: remove unused code 2024-12-21 19:00:20 +08:00
Fu Diwei
ef9ddd27a5 chore: remove unused code 2024-12-21 12:46:22 +08:00
Fu Diwei
b6203e57ed fix: fix typo 2024-12-20 23:11:45 +08:00
Fu Diwei
3fcea4ba2f refactor: clean code 2024-12-20 23:07:40 +08:00
Fu Diwei
a51f85826c chore: remove unused code 2024-12-20 23:00:05 +08:00
Fu Diwei
c846945905 refactor(deployer): reimplement deploy service 2024-12-20 22:59:04 +08:00
Fu Diwei
e2af21e0e1 fix: could not deploy again when certificate is not expired 2024-12-20 22:59:00 +08:00
Fu Diwei
929250810f chore: remove unused code 2024-12-20 21:45:07 +08:00
Fu Diwei
cb162e063d chore: remove unused code 2024-12-20 21:23:55 +08:00
Fu Diwei
63ffb9df14 fix(ui): tsc-check error 2024-12-20 21:01:24 +08:00
Fu Diwei
a917d6c2c5 feat(ui): new SettingsSSLProvider using antd 2024-12-20 20:42:46 +08:00
Fu Diwei
9e1e0dee1d fix(ui): couldn't detect form changed in NotifyChannels 2024-12-20 14:08:30 +08:00
Fu Diwei
7c1a2d5f91 feat(ui): new SettingsNotification using antd 2024-12-20 13:56:29 +08:00
Fu Diwei
cae33cfc4f fix(ui): duplicate form names 2024-12-20 12:06:30 +08:00
Fu Diwei
d143df3f9f fix(ui): deep compare when model change in AccessEditForm 2024-12-19 21:34:17 +08:00
Fu Diwei
84a3817b15 feat(ui): disable autocomplete in AccessEditForm 2024-12-19 13:11:32 +08:00
Fu Diwei
525eb83d1e feat(ui): new SettingsPassword using antd 2024-12-19 11:59:13 +08:00
Fu Diwei
8b7295d77e feat(ui): new SettingsAccount using antd 2024-12-19 10:50:42 +08:00
Fu Diwei
df57c196e9 build(ui): config babel 2024-12-19 10:18:04 +08:00
Fu Diwei
5ea5473bdd refactor(ui): clean code 2024-12-19 09:44:09 +08:00
Fu Diwei
85faf8d517 chore: remove unused code 2024-12-18 21:24:39 +08:00
Fu Diwei
abe6dbb5a2 feat(ui): new Settings layout using antd 2024-12-18 21:22:25 +08:00
Fu Diwei
afa446aabe feat(ui): fixed header & sider 2024-12-18 20:45:27 +08:00
Fu Diwei
2712f9a3f4 feat(ui): new AccessSelect component using antd 2024-12-18 16:10:47 +08:00
Fu Diwei
c40de5d3b2 build: vite.config.ts 2024-12-18 13:24:35 +08:00
Fu Diwei
2b1da81b98 chore 2024-12-18 13:19:55 +08:00
Fu Diwei
d693d26323 refactor: clean code 2024-12-18 10:27:55 +08:00
Fu Diwei
2374bb56fa refactor: clean code 2024-12-18 10:20:32 +08:00
Fu Diwei
df71782719 fix: #322 2024-12-17 19:25:06 +08:00
Fu Diwei
599e718003 feat(ui): props guard 2024-12-17 19:25:00 +08:00
Fu Diwei
1cad816b17 fix: render error when notify template is empty 2024-12-17 19:15:05 +08:00
Fu Diwei
0fa6d2980b chore: remove unused code 2024-12-17 19:11:28 +08:00
Fu Diwei
c27818b3b0 feat(ui): new AccessEditForm using antd 2024-12-17 19:11:19 +08:00
Fu Diwei
047b3bc079 feat: normalize provider names 2024-12-17 17:11:36 +08:00
Fu Diwei
70e6920288 refactor(ui): clean code 2024-12-16 13:37:10 +08:00
Fu Diwei
b5739c663d feat(ui): new AccessProviderSelect component using antd 2024-12-12 16:49:12 +08:00
Fu Diwei
220d98a668 feat(ui): show more details in CertificateDetail 2024-12-12 09:48:50 +08:00
yoan
419b6eb626 fix build error 2024-12-11 22:53:37 +08:00
yoan
9764fb481f update version 2024-12-11 22:29:10 +08:00
yoan
ba01edc691 fix build error 2024-12-11 22:28:04 +08:00
yoan
fba647313d update version 2024-12-11 22:00:01 +08:00
yoan
2f146fffdc Merge branch 'next' of github.com:usual2970/certimate into next 2024-12-11 21:57:57 +08:00
yoan
7654c79d12 Merge branch 'fudiwei-feat/new-workflow-ui' into next 2024-12-11 21:57:25 +08:00
yoan
cf35dbfd6e Update site name width 2024-12-11 21:56:01 +08:00
yoan
a6c002146c Merge branch 'feat/new-workflow-ui' of github.com:fudiwei/certimate into fudiwei-feat/new-workflow-ui 2024-12-11 21:26:42 +08:00
Fu Diwei
bb3009a124 refactor(ui): refactor accesses state using zustand store 2024-12-11 19:55:50 +08:00
Fu Diwei
b744363736 refactor(ui): refactor emails state using zustand store 2024-12-11 16:42:23 +08:00
RHQYZ
35c25987cd Update README_EN.md 2024-12-10 20:03:41 +08:00
RHQYZ
ca91a9e089 Update README.md 2024-12-10 20:01:27 +08:00
Fu Diwei
83ba3d4450 fix: #361 2024-12-10 19:23:48 +08:00
Fu Diwei
8fe0d342aa refactor(ui): improve i18n 2024-12-10 16:37:24 +08:00
Fu Diwei
a4eff0b408 feat(ui): enhance certificate downloading 2024-12-09 19:42:56 +08:00
Fu Diwei
7b85da901d feat(ui): responsive table 2024-12-09 19:32:09 +08:00
Fu Diwei
be27789ea8 feat(ui): responsive sider menu 2024-12-09 19:25:35 +08:00
Fu Diwei
07a443f6c4 refactor(ui): clean code 2024-12-09 17:48:44 +08:00
Fu Diwei
588e89e8fe feat(ui): copied to clipboard message 2024-12-09 17:38:34 +08:00
Fu Diwei
789c120fc9 feat(ui): antd theme 2024-12-09 17:04:02 +08:00
Fu Diwei
fdfe54b6da feat(ui): antd i18n 2024-12-09 16:09:35 +08:00
Fu Diwei
3b50741f19 chore(ui): clean code 2024-12-09 15:17:55 +08:00
Fu Diwei
c5498b92a2 feat(ui): optimize table UI 2024-12-09 13:19:25 +08:00
Fu Diwei
048150d779 feat(ui): new Dashboard UI using antd 2024-12-09 13:10:48 +08:00
Fu Diwei
7db933199a feat(ui): optimize table UI 2024-12-08 21:10:22 +08:00
Fu Diwei
5c6be439e8 feat(ui): optimize table UI 2024-12-08 11:55:30 +08:00
Fu Diwei
4e0134b70a feat(ui): new CertificateDetail UI using antd 2024-12-07 17:11:36 +08:00
Fu Diwei
d6ddf8e9f4 feat(ui): new Layout UI using antd 2024-12-07 13:47:02 +08:00
Fu Diwei
2facb160aa feat(ui): new Login UI using antd 2024-12-06 19:44:29 +08:00
Fu Diwei
b44b8d09b2 feat(ui): new AccessList UI using antd 2024-12-06 09:54:44 +08:00
Fu Diwei
65d9c6fe2f feat(ui): new CertificateList UI using antd 2024-12-05 21:52:27 +08:00
Fu Diwei
c522196029 feat(ui): new WorkflowList UI using antd 2024-12-04 21:55:52 +08:00
yoan
a5d097e860 Ensure branches execute independently without affecting each other 2024-12-02 08:54:25 +08:00
Fu Diwei
668f6ee36f feat(ui): use ant-design 2024-11-25 21:28:38 +08:00
Fu Diwei
4f2363230d chore: fix typo 2024-11-25 21:28:07 +08:00
Fu Diwei
2b93552d1d chore: comments 2024-11-25 21:22:20 +08:00
yoan
124af0b76d update workflow file 2024-11-24 21:11:46 +08:00
yoan
972df7c167 migration 2024-11-24 20:16:55 +08:00
yoan
b4c17a6a12 update version 2024-11-24 20:07:00 +08:00
yoan
92c03cbdf9 fix conflict 2024-11-24 20:06:30 +08:00
yoan
220478cd31 update version 2024-11-24 20:01:40 +08:00
yoan
df905ade88 workflow ajustment 2024-11-24 20:01:04 +08:00
yoan
9ff3e22c80 details improvement and unnecessary files deletion 2024-11-24 13:36:17 +08:00
yoan
37df882ed3 improve multi language 2024-11-23 12:55:31 +08:00
yoan
47050769fc fix conflict 2024-11-22 11:16:54 +08:00
Yoan.liu
65df759275 Merge pull request #348 from fudiwei/feat/deployer
feat: deployers
2024-11-22 11:14:24 +08:00
yoan
86761bd3a0 Certificate displaying and monitoring 2024-11-22 10:59:57 +08:00
Fu Diwei
a842b6b925 fix: illegal arguments 2024-11-21 20:23:01 +08:00
yoan
09e4b24445 certificate display 2024-11-21 13:17:39 +08:00
Fu Diwei
4916757d59 feat: add Deployer factory 2024-11-21 11:23:15 +08:00
Fu Diwei
30b66adc3b refactor: replace Append* to Log* in DeployerLogger 2024-11-21 10:35:45 +08:00
Fu Diwei
13582d1a7b test: add unit test cases 2024-11-21 10:29:04 +08:00
Fu Diwei
0b9312b549 feat: implement more Deployer 2024-11-20 23:51:26 +08:00
Fu Diwei
bde51d8d38 feat: implement more Deployer 2024-11-20 22:58:01 +08:00
Fu Diwei
643a666853 feat: implement more Deployer 2024-11-20 21:02:29 +08:00
yoan
2d10fa0218 Save and display execution records 2024-11-20 15:47:51 +08:00
Fu Diwei
a59184ae5f fix: update GetValueOrDefault util functions to return default value for zero values 2024-11-20 07:49:50 +08:00
Fu Diwei
82807fcc1b refactor: clean code 2024-11-19 22:43:15 +08:00
Fu Diwei
a6c93ef9b8 test: fix typo 2024-11-19 22:11:47 +08:00
Fu Diwei
6a151865f7 feat: implement k8s secret Deployer 2024-11-19 22:04:00 +08:00
Fu Diwei
414d8d140e test: use flag arguments in test cases for Notifier and Deployer 2024-11-19 21:18:36 +08:00
Fu Diwei
51fb9dca58 test: add some unit test cases for new Deployer 2024-11-19 20:03:51 +08:00
Fu Diwei
6367785b1b feat: implement local, ssh, webhook Deployer 2024-11-19 19:09:48 +08:00
yoan
03b2a9da66 Implement complete workflow execution process 2024-11-19 16:02:31 +08:00
Fu Diwei
aa7fb7da06 Merge branch 'main' into feat/deployer 2024-11-19 09:09:38 +08:00
Fu Diwei
26d11de249 feat: add deployer interface 2024-11-19 09:08:49 +08:00
yoan
a9d5b53460 Merge branch 'main' into feat/workflow 2024-11-19 09:07:37 +08:00
yoan
0daa9f1882 v0.2.21 2024-11-19 09:07:08 +08:00
yoan
f799740d70 fix conflict 2024-11-18 20:22:21 +08:00
yoan
56886dcfe9 Merge branch 'LeoChen98-fix-reapply-when-domain-list-changed' 2024-11-18 20:03:16 +08:00
yoan
81e1e4a7ff validity duration 2024-11-18 20:03:11 +08:00
yoan
9b5256716f Merge branch 'fix-reapply-when-domain-list-changed' of github.com:LeoChen98/certimate into LeoChen98-fix-reapply-when-domain-list-changed 2024-11-18 19:58:36 +08:00
usual2970
446bf80f1d Merge pull request #346 from jarod/main
feat: add deployer BytePlus CDN
2024-11-18 19:43:58 +08:00
yoan
775b12aec1 Add workflow execution process 2024-11-18 19:40:24 +08:00
Jarod Liu
6a80455c6c fix: byteplus access provider 2024-11-18 10:51:51 +08:00
Fu Diwei
43b2ff7957 refactor: extract x509 transformer utils 2024-11-18 09:12:15 +08:00
Fu Diwei
295b7779ee refactor: clean code 2024-11-18 09:10:28 +08:00
Jarod Liu
d1df088662 fix: 补充Provider Access 的 UI 实现 2024-11-16 09:52:28 +08:00
Jarod Liu
2b0f7aaf8a feat: add deployer BytePlus CDN 2024-11-16 09:18:58 +08:00
Leo Chen
3265dd76ab edit comments for the forward changes 2024-11-15 20:45:08 +08:00
Leo Chen
d1d7b44303 Invert the changed logic to match the function name 2024-11-15 20:37:36 +08:00
Leo Chen
56eced3813 Invert the boolean value to match the function name 2024-11-15 20:36:47 +08:00
yoan
bde2147dd3 fix conflict 2024-11-15 10:27:10 +08:00
yoan
c853f2976f v0.2.20 2024-11-15 08:07:37 +08:00
yoan
8901f5d40e improve data display 2024-11-15 08:06:39 +08:00
usual2970
b66931003f Merge pull request #342 from belier-cn/volcengine-cdn
feat: add volcengine cdn deployer
2024-11-15 08:05:27 +08:00
Leo Chen
9a75d2ac8f add key algorithm check 2024-11-15 00:33:09 +08:00
yoan
9132d47f4d display workflow data 2024-11-14 14:52:02 +08:00
belier
42c5aea3f7 docs: update README_EN.md 2024-11-14 14:28:39 +08:00
belier
e2fd9c4cee style: modify variable name 2024-11-14 14:28:35 +08:00
belier
f847b7ff62 improvement: improve certificate fingerprint comparison 2024-11-14 14:19:00 +08:00
belier
9eae8f5077 feat: add volcengine cdn deployer 2024-11-14 13:39:23 +08:00
usual2970
2bacf76664 Merge pull request #339 from belier-cn/main
feat: add volcengine dns provider and add volcengine live deployer
2024-11-14 09:42:26 +08:00
usual2970
b2030caedc Merge pull request #337 from fudiwei/bugfix/syntax-error
fix switch-case syntax error
2024-11-14 09:35:58 +08:00
usual2970
956c975c6d Merge pull request #333 from JiangJamm/feat/notify_setting_expand
feat: 使系统设置中的消息推送设置列表打开后能够关闭
2024-11-14 09:34:52 +08:00
Leo Chen
41bd321a4f fixed: not reapply when domain list changed
fixed #334
2024-11-13 18:52:29 +08:00
Leo Chen
952e9687d0 fix misspelling var name 2024-11-13 17:58:56 +08:00
belier
c298f8b952 docs: Add Volcengine Information to README.md 2024-11-13 16:18:04 +08:00
belier
e2562a5251 feat: add volcengine dns provider and add volcengine live deployer 2024-11-13 15:36:46 +08:00
Fu Diwei
dbdb40baf9 fix: fix switch-case syntax error 2024-11-13 13:44:44 +08:00
yoan
52f40d982d add access to deployment 2024-11-13 13:20:47 +08:00
yoan
fd04cec606 Merge branch 'main' into feat/workflow 2024-11-13 08:41:25 +08:00
yoan
2ff923dd1b v0.2.19 2024-11-13 08:16:19 +08:00
usual2970
f4f13f91f2 Merge pull request #331 from fudiwei/bugfix/qiniu-wildcard-domain
bugfix #330
2024-11-13 08:14:32 +08:00
usual2970
034aa980e6 Merge pull request #329 from fudiwei/bugfix/aliyun-clb-deploy-error
bugfix #326
2024-11-13 08:14:19 +08:00
usual2970
6ac7a51ce0 Merge pull request #328 from fudiwei/bugfix/tencentcloud-deploy-config-not-saving
bugfix #324
2024-11-13 08:14:05 +08:00
usual2970
cf0c0e3e2c Merge pull request #327 from LeoChen98/fix-tencent-cos-instance-not-found
fixed: instance not found when deploying tencent COS
2024-11-13 08:13:49 +08:00
JiangJamm
1b899575e0 feat: 使系统设置中的消息推送设置列表打开后能够关闭 2024-11-13 01:10:26 +08:00
Fu Diwei
23e5cb5669 fix: #330 2024-11-12 21:41:06 +08:00
yoan
ee9578b273 delete mail 2024-11-12 21:40:02 +08:00
Fu Diwei
e4ba4c9b37 fix: #326 2024-11-12 20:35:31 +08:00
Fu Diwei
9ed64bdc9a fix: #324 2024-11-12 20:20:54 +08:00
Leo Chen
e9b6fb55ff fixed: instance possible not found when deploying tencent CLB via SSL api
修复了重构导致腾讯云CLB通过SSL接口部署时可能找不到实例的bug
2024-11-12 17:59:13 +08:00
Leo Chen
80caf881ae fixed: instance not found when deploying tencent COS
修复了重构导致腾讯云COS部署时找不到实例的bug
2024-11-12 17:56:41 +08:00
yoan
35c0ed2ba5 workflow data save 2024-11-12 13:16:23 +08:00
usual2970
c36db3545f Merge pull request #321 from fudiwei/feat/notifier
feat: notifiers
2024-11-11 18:16:30 +08:00
yoan
1ea0ba18cd workflow multi languages 2024-11-11 15:50:36 +08:00
yoan
327c83cbc8 Merge branch 'main' into feat/workflow 2024-11-11 08:19:42 +08:00
yoan
a367585ab4 v0.2.18 2024-11-11 07:58:13 +08:00
Fu Diwei
2994cb5c65 test: add unit test case for email notifier 2024-11-10 20:28:01 +08:00
Fu Diwei
1bedb31a3c fix: fix typo 2024-11-10 20:06:18 +08:00
Fu Diwei
8fecebc254 feat: show loading button when pushing test notifications 2024-11-10 20:00:19 +08:00
Fu Diwei
44497a0969 feat: new UI for notify settings 2024-11-10 19:52:50 +08:00
usual2970
5362371bda Merge pull request #319 from fudiwei/bugfix/aliyun-api-error
bugfix #318
2024-11-10 19:40:40 +08:00
Fu Diwei
8b04e96a7d feat: new UI for email notify settings 2024-11-10 18:21:43 +08:00
Fu Diwei
5d93334426 refactor: re-implement logic of notify 2024-11-10 18:03:20 +08:00
yoan
be84f3314f impprove frontend 2024-11-10 15:11:21 +08:00
Fu Diwei
150b666d4b refactor: maps utils 2024-11-09 20:46:49 +08:00
Fu Diwei
94579d65c4 refactor: clean code 2024-11-09 20:29:13 +08:00
Fu Diwei
551b06b4e8 feat: notifier 2024-11-09 20:06:22 +08:00
Fu Diwei
76fc47a274 Merge branch 'main' into feat/notifier 2024-11-09 12:14:21 +08:00
yoan
07b5760986 Merge branch 'main' into feat/workflow 2024-11-09 12:04:24 +08:00
yoan
35e1bfcd7f Update readme 2024-11-09 11:37:34 +08:00
yoan
b06ffc0eef worklfow 2024-11-09 11:31:44 +08:00
Fu Diwei
24df7913fe feat: support aliyun global ALB/NLB 2024-11-09 09:54:49 +08:00
Fu Diwei
83674e4b35 refactor: ensure compile-time check for Uploader implementations 2024-11-09 09:47:14 +08:00
Fu Diwei
22d3aeb7b5 fix: #318 2024-11-09 09:41:05 +08:00
yoan
8809eef2ce Merge branch 'main' into feat/workflow 2024-11-08 21:27:20 +08:00
yoan
cf005711c0 v0.2.17 2024-11-08 08:11:04 +08:00
usual2970
0a00d0c52f Merge pull request #314 from fudiwei/bugfix/dogecloud-api-error
bugfix #313
2024-11-08 08:10:18 +08:00
usual2970
9aa17a0395 Merge pull request #315 from fudiwei/bugfix/qiniu-panic
bugfix #304
2024-11-08 08:09:41 +08:00
yoan
e4d190f1e7 Merge branch 'main' into feat/workflow 2024-11-07 21:19:21 +08:00
Fu Diwei
65ecdf7dc2 update README 2024-11-07 17:36:41 +08:00
Fu Diwei
0dfa5994cc fix: #304 2024-11-07 17:35:43 +08:00
Fu Diwei
5d2844fdb6 fix: #313 2024-11-07 15:01:46 +08:00
yoan
44332b9d07 v0.2.16 2024-11-07 08:09:25 +08:00
yoan
9b8e73f1de workflow 2024-11-07 08:08:50 +08:00
usual2970
20a23e148c Merge pull request #309 from fudiwei/bugfix/dogecloud-api-error
bugfix #308
2024-11-07 08:06:55 +08:00
yoan
076f0d5de9 Merge branch 'main' into feat/workflow 2024-11-06 14:55:20 +08:00
RHQYZ
0bcb6206f4 fix #308 2024-11-06 11:07:24 +08:00
yoan
943b9827ee v0.2.15 2024-11-06 07:12:48 +08:00
usual2970
741f3ec212 Merge pull request #306 from fudiwei/bugfix/dogecloud-api-error
bugfix #303
2024-11-06 07:08:12 +08:00
yoan
613b6839b8 workflow 2024-11-05 21:00:53 +08:00
Fu Diwei
8549a17675 fix: #303 2024-11-05 18:16:21 +08:00
yoan
718cfccbea resolve new sftp client failure 2024-11-05 08:35:37 +08:00
yoan
2458fa26d8 v0.2.14 2024-11-05 08:30:28 +08:00
yoan
ac24684d2b Merge branch 'main' of github.com:usual2970/certimate 2024-11-05 08:29:58 +08:00
yoan
106dbd9538 Merge branch 'fudiwei-feat/cloud-cdn' 2024-11-05 08:29:30 +08:00
yoan
f9efb2b800 migration 2024-11-05 08:28:35 +08:00
usual2970
897d124d5b Merge pull request #299 from fudiwei/bugfix/ssh-jks
bugfix #298
2024-11-05 08:15:13 +08:00
Fu Diwei
34daf9ccac refactor: clean code 2024-11-04 12:54:23 +08:00
Fu Diwei
269a97e81e feat: add baiducloud cdn deployer 2024-11-04 12:44:53 +08:00
Fu Diwei
2fd57621d8 fix: #298 2024-11-04 11:20:35 +08:00
Fu Diwei
76de837214 feat: add baiducloud provider 2024-11-04 11:11:00 +08:00
Fu Diwei
1e41020728 feat: add dogecloud cdn deployer 2024-11-04 10:34:05 +08:00
Fu Diwei
8a78e49bf0 feat: add dogecloud provider 2024-11-04 10:30:18 +08:00
yoan
e6726e4c02 v0.2.13 2024-11-04 08:07:05 +08:00
yoan
76330a4a1a v0.2.12 2024-11-04 07:49:00 +08:00
usual2970
7e5f0097e4 Merge pull request #296 from usual2970/hotfix/email
fix: resolve email notification delivery failure
2024-11-02 13:09:37 +08:00
yoan
18e1c02d1c fix: resolve email notification delivery failure 2024-11-02 10:17:16 +08:00
usual2970
28992f178e Merge pull request #294 from funnyzak/bark_notify
feat: add Bark notifier
2024-11-02 09:52:13 +08:00
usual2970
c41f34c352 Merge pull request #276 from fudiwei/feat/cloud-load-balance
feat: tencent clb deployer
2024-11-02 09:46:42 +08:00
Fu Diwei
6b5580a30c refactor: clean code 2024-11-01 15:56:22 +08:00
Fu Diwei
1dee14e32d refactor: adjust project structure 2024-11-01 15:54:05 +08:00
Fu Diwei
1e3c4881d0 refactor: remove unused certificate name in TencentCloudSSLUploader 2024-11-01 15:33:02 +08:00
Leon
657964cda4 feat: add Bark notification channel and related settings 2024-11-01 11:35:09 +08:00
Fu Diwei
893aac916c feat(ui): show deploy provider name rather than access provider name in DeployList 2024-10-31 20:25:06 +08:00
Fu Diwei
68da6cf3ae fix: fix import cycle 2024-10-31 20:03:04 +08:00
Fu Diwei
0d96ea9eef refactor: deprecate internal/deployer/deployer.getDeployVariables 2024-10-31 19:59:21 +08:00
Fu Diwei
0ceb44a7cd refactor: deprecate internal/utils/rand.RandStr 2024-10-31 19:53:48 +08:00
Fu Diwei
4fec0036cb refactor: fix typo 2024-10-31 18:25:22 +08:00
Fu Diwei
f82eee4636 refactor: clean code 2024-10-31 14:30:16 +08:00
Fu Diwei
260cfb96ec refactor(ui): declare deploy config params 2024-10-31 14:27:11 +08:00
Fu Diwei
f71a519674 refactor: clean code 2024-10-31 13:41:21 +08:00
Fu Diwei
369c146eca feat: support tencent clb deployment in multiple ways 2024-10-31 13:24:43 +08:00
Fu Diwei
83264a6946 refactor: clean code 2024-10-31 11:37:16 +08:00
Fu Diwei
3c3d4e9109 refactor: extend qiniu sdk 2024-10-31 11:37:03 +08:00
Fu Diwei
ce55365292 refactor: extend huaweicloud cdn sdk 2024-10-31 10:14:27 +08:00
Fu Diwei
be495839b6 Merge branch 'main' into feat/cloud-load-balance 2024-10-31 09:14:57 +08:00
usual2970
a27a9f55a7 Merge pull request #284 from usual2970/feat/ui-1030
Fix the issue where long domain names or titles overlap the next column.
2024-10-31 08:15:22 +08:00
usual2970
10e14caf35 Merge pull request #285 from LeoChen98/fix-tencent-cos-locales-loss
fix: tencent cos ui locales loss
2024-10-31 08:15:04 +08:00
Fu Diwei
59af246479 refactor: clean code 2024-10-30 19:37:44 +08:00
Leo Chen
1f52eaca01 fix: tencent cos ui locales loss 2024-10-30 17:09:11 +08:00
yoan
d833f4b5ff fix cos region validate 2024-10-30 16:08:32 +08:00
yoan
bfee39049d Merge branch 'LeoChen98-feat-add-netsh-preset' 2024-10-30 12:29:07 +08:00
yoan
b4599df6c6 code format 2024-10-30 12:28:59 +08:00
yoan
261c6f6956 Merge branch 'feat-add-netsh-preset' of github.com:LeoChen98/certimate into LeoChen98-feat-add-netsh-preset 2024-10-30 12:26:06 +08:00
yoan
b97d77c848 Fix the issue where long domain names or titles overlap the next column. 2024-10-30 11:57:16 +08:00
yoan
c1cefe0e7f v0.2.11 2024-10-30 11:07:59 +08:00
yoan
55b77fdf5c Fix the issue where the deployment type could not be selected 2024-10-30 11:03:41 +08:00
yoan
16967c4ab1 fix tencent cdn deploy 2024-10-30 09:31:51 +08:00
yoan
61a4fd8657 v0.2.10 2024-10-30 07:04:05 +08:00
Leo Chen
67ca7e3097 feat: add netsh preset
新增本地Windows下使用netsh绑定证书的预设
2024-10-29 21:43:20 +08:00
Fu Diwei
26fa8e75bd refactor: clean code 2024-10-29 21:32:48 +08:00
Fu Diwei
aeaa45b713 Merge branch 'main' into feat/cloud-load-balance 2024-10-29 09:12:39 +08:00
yoan
edeac86f06 Merge branch 'fudiwei-feat/multiple-certificate-formats' 2024-10-29 08:46:06 +08:00
yoan
4e0c23165f fix conflict 2024-10-29 08:45:51 +08:00
usual2970
feb851a3fc Merge pull request #273 from LeoChen98/enhance-tencent-cdn-dupe-deploy
enhance: resolve error on tencent cdn dupe deployment
2024-10-29 08:39:57 +08:00
usual2970
3103d60508 Merge pull request #274 from PittyXu/feat/k8s
fix: k8s部署更新报错
2024-10-29 08:39:15 +08:00
usual2970
53be6b5f5b Merge pull request #272 from LeoChen98/feat-add-mail-push
feat: add mail push
2024-10-29 08:38:10 +08:00
usual2970
9d3e0d1090 Merge pull request #278 from usual2970/feat/searchable_select
feat: Searchable when selecting authorization type
2024-10-29 08:37:53 +08:00
yoan
f8aef129cf Searchable when selecting authorization type 2024-10-28 22:52:25 +08:00
Leo Chen
c419b2c8b4 use slice pkg 2024-10-28 20:28:13 +08:00
Fu Diwei
e1a3a3e7c7 refactor: clean code 2024-10-28 14:15:33 +08:00
Fu Diwei
b47a1a13cb feat: support jks format 2024-10-28 11:49:44 +08:00
徐雪君
3397f424bc fix: k8s部署更新报错 #266 2024-10-28 11:15:08 +08:00
yoan
48672d1a44 v0.2.9 2024-10-28 08:48:30 +08:00
Leo Chen
38dc8a63d9 enhance: resolve error on tencent cdn dupe deployment
优化:腾讯云cdn重复部署报错的问题
2024-10-27 23:48:52 +08:00
Fu Diwei
009e8fb976 feat: preset scripts on deployment to local 2024-10-27 21:10:19 +08:00
Fu Diwei
6d7a91f49b refactor: clean code 2024-10-27 20:44:38 +08:00
yoan
9d4d14db06 Update README.md 2024-10-27 20:42:47 +08:00
Leo Chen
c9f347f77a fix mail push onchange 2024-10-27 20:27:46 +08:00
Leo Chen
0396d8222e feat: add mail push
新增电子邮箱推送
2024-10-27 20:21:34 +08:00
Fu Diwei
305f3de50f Merge branch 'main' into feat/multiple-certificate-formats 2024-10-27 20:17:04 +08:00
yoan
ffacfe0f42 Merge branch 'LeoChen98-feat-serverchan-push-tube' 2024-10-27 09:18:46 +08:00
yoan
be9e66c7d3 Merge branch 'feat-serverchan-push-tube' of github.com:LeoChen98/certimate into LeoChen98-feat-serverchan-push-tube 2024-10-27 09:15:12 +08:00
yoan
1238508bdb Merge branch 'fudiwei-feat/cloud-load-balance' 2024-10-27 09:12:05 +08:00
yoan
1ab5c4035a fix conflict 2024-10-27 09:10:12 +08:00
yoan
67fa9d91bf Merge branch 'PittyXu-feat/k8s' 2024-10-27 08:38:44 +08:00
yoan
dc5f9abf20 detail ajustments 2024-10-27 08:37:42 +08:00
yoan
7240a42fbc Merge branch 'feat/k8s' of github.com:PittyXu/certimate into PittyXu-feat/k8s 2024-10-27 08:35:36 +08:00
yoan
6fbb6d4992 Merge branch 'LeoChen98-feat-tecent-ecdn-teo-deploy' 2024-10-27 08:33:00 +08:00
yoan
86838f305b detail ajustments 2024-10-27 08:32:48 +08:00
yoan
1b1b5939c5 Merge branch 'feat-tecent-ecdn-teo-deploy' of github.com:LeoChen98/certimate into LeoChen98-feat-tecent-ecdn-teo-deploy 2024-10-27 08:07:48 +08:00
Leo Chen
ffdd61b5ee feat: add ServerChan notifier
新增Server酱通知
2024-10-27 04:01:42 +08:00
Fu Diwei
adad5d86ba feat: support specified format on deployment to local/ssh 2024-10-27 00:19:34 +08:00
Fu Diwei
e7870e2b05 feat: support specified shell on deployment to local 2024-10-26 22:22:28 +08:00
徐雪君
548cbbfdd4 feat: k8s部署支持ServiceAccount权限 2024-10-26 22:15:16 +08:00
Fu Diwei
da4715e6dc fix: fix aliyun nlb endpoint 2024-10-26 13:18:15 +08:00
Fu Diwei
506ab4f18e feat: support quic listener in deployment to aliyun alb 2024-10-26 13:15:01 +08:00
Fu Diwei
d87026d5be feat: add aliyun nlb deployer 2024-10-26 12:52:55 +08:00
Fu Diwei
1690963aaf feat: add aliyun alb deployer 2024-10-26 12:40:45 +08:00
Fu Diwei
20d2c5699c feat: add aliyun clb deployer 2024-10-26 00:31:38 +08:00
Fu Diwei
e660e9cad1 feat: add aliyun slb uploader 2024-10-25 23:13:33 +08:00
Fu Diwei
26d7b0ba03 refactor: clean code 2024-10-25 23:03:52 +08:00
Leo Chen
ee097b3135 update README for tencent TEO support 2024-10-25 22:21:30 +08:00
Leo Chen
f5052e9a58 fix the missing parentheses 2024-10-25 22:18:40 +08:00
Leo Chen
3b3376899c add feat: tencent TEO deploy support
新增腾讯TEO(Edge One)部署方式
2024-10-25 22:16:27 +08:00
Leo Chen
a24a3595fa feat: add tencent ECDN deploy 2024-10-25 18:47:41 +08:00
Leo Chen
6a14d801f1 fix type incompatible error 2024-10-25 18:32:45 +08:00
yoan
332c5c5127 fix error type 2024-10-25 18:32:32 +08:00
usual2970
f9568f1a4a Merge pull request #254 from fudiwei/feat/cloud-load-balance
feat: huaweicloud elb deployer
2024-10-25 17:43:11 +08:00
usual2970
b458720dca Merge pull request #257 from belier-cn/main
feat: keep qiniu cdn https configuration
2024-10-25 16:16:20 +08:00
belier
935a320100 feat: keep qiniu cdn https configuration 2024-10-25 14:45:48 +08:00
yoan
361d0de17c v0.2.8 2024-10-25 08:10:05 +08:00
Fu Diwei
024b3c936e Merge branch 'main' into feat/cloud-load-balance 2024-10-24 22:45:25 +08:00
Fu Diwei
dc720a5d99 feat: add huaweicloud elb deployer 2024-10-24 22:37:55 +08:00
Fu Diwei
af3e20709d refactor: clean code 2024-10-24 21:42:39 +08:00
yoan
ea9e9165b6 Fix the issue where log information is not displayed. 2024-10-24 21:03:57 +08:00
Fu Diwei
ee531dd186 fix: aliyun oss deploy config validation error 2024-10-24 20:49:51 +08:00
yoan
51abe8de56 Merge branch 'zzci-main' 2024-10-24 20:47:00 +08:00
yoan
e2254faf15 Reuse the x509 package 2024-10-24 20:44:41 +08:00
Fu Diwei
cea6be37dc feat: allow set a different region on deployment to huaweicloud cdn 2024-10-24 20:16:23 +08:00
Roy
46dccb176e fix typo, get annotations from cert. 2024-10-24 18:39:18 +08:00
Roy
5411b9cb92 change annotations to certimage. 2024-10-24 17:06:57 +07:00
Roy
9f6ea410af Update k8s_secret.go 2024-10-24 17:05:05 +07:00
Roy
528a3d9da8 support create secret, add cert annotations. 2024-10-24 17:56:36 +08:00
yoan
564eb48ebe update dark mod stype 2024-10-24 08:59:17 +08:00
usual2970
92a6b179d4 Merge pull request #247 from LeoChen98/feat-tencent-clb
feat: add support for tencent CLB
2024-10-24 08:03:28 +08:00
Leo Chen
83393a4ee1 update readme for tencent clb support 2024-10-24 00:00:24 +08:00
Leo Chen
6875151717 fix tencent clb deploy failed
- 新增region参数
- 新增配置说明
2024-10-23 23:56:22 +08:00
usual2970
2a8c6cf033 Merge pull request #244 from usual2970/feat/gts
Support Google Trust Services
2024-10-23 21:13:50 +08:00
Leo Chen
7544286b0f add support for tencent CLB
新增腾讯云CLB负载均衡配置支持
2024-10-23 18:57:12 +08:00
Leo Chen
7c685646da fix tencent cos ui placeholder 2024-10-23 18:48:01 +08:00
Leo Chen
d82a9c9253 fix tencent cos ui onload verify 2024-10-23 18:45:36 +08:00
Leo Chen
59584a2961 fix tencent cos input verify 2024-10-23 18:40:52 +08:00
Leo Chen
195aa54cdc add wildcase domain supported ui label 2024-10-23 18:21:18 +08:00
Leo Chen
4b324e6a22 fix tencent COS ui 2024-10-23 18:19:35 +08:00
Leo Chen
0e575a0ce7 rename tencent_cos.go 2024-10-23 17:40:32 +08:00
yoan
7ab8517a93 Handle concurrency issues in a simple way. 2024-10-23 17:32:35 +08:00
yoan
1dca6ecf8d An account for many customers 2024-10-23 16:25:21 +08:00
yoan
8bec234fe8 gts support 2024-10-23 13:22:17 +08:00
839 changed files with 62596 additions and 25267 deletions

View File

@@ -8,3 +8,7 @@ indent_size = 2
indent_style = space
trim_trailing_whitespace = true
insert_final_newline = true
[*.go]
indent_size = 2
indent_style = tab

15
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,15 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
thanks_dev: # Replace with a single thanks.dev username
custom: ["https://profile.ikit.fun/sponsors/"]

View File

@@ -1,15 +1,17 @@
name: Docker Image CI
name: Docker Image CI (stable versions)
on:
push:
tags:
- "*"
- "v[0-9]*"
- "!v*alpha*"
- "!v*beta*"
workflow_dispatch:
inputs:
tag:
description: "Tag version to be used for Docker image"
required: true
default: "v0.1.9"
default: "latest"
jobs:
build-and-push:
@@ -35,14 +37,12 @@ jobs:
- name: Log in to DOCKERHUB
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Log in to ALIYUNCS
uses: docker/login-action@v3
with:
registry: registry.cn-shanghai.aliyuncs.com
username: ${{ secrets.DOCKER_USERNAME }}
@@ -56,4 +56,3 @@ jobs:
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}

61
.github/workflows/push_image_next.yml vendored Normal file
View File

@@ -0,0 +1,61 @@
name: Docker Image CI (preview versions)
on:
push:
tags:
- "v[0-9]*-alpha*"
- "v[0-9]*-beta*"
workflow_dispatch:
inputs:
tag:
description: "Tag version to be used for Docker image"
required: true
default: "next"
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: |
usual2970/certimate
registry.cn-shanghai.aliyuncs.com/usual2970/certimate
tags: |
type=ref,event=tag,pattern={{version}}
flavor: |
latest=false
- name: Log in to DOCKERHUB
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Log in to ALIYUNCS
uses: docker/login-action@v3
with:
registry: registry.cn-shanghai.aliyuncs.com
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}

View File

@@ -1,9 +1,9 @@
name: basebuild
name: Base Build
on:
push:
tags:
- "*"
- "v[0-9]*"
jobs:
goreleaser:
@@ -22,9 +22,9 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ">=1.22.5"
go-version: ">=1.23.0"
- name: Build Admin dashboard UI
- name: Build WebUI
run: npm --prefix=./ui ci && npm --prefix=./ui run build
- name: Run GoReleaser

6
.gitignore vendored
View File

@@ -15,8 +15,6 @@ vendor
pb_data
build
main
/ui/dist/*
!/ui/dist/.gitkeep
./dist
./certimate
/dist
/docker/data
/certimate

View File

@@ -11,10 +11,11 @@
"gopls": {
"formatting.gofumpt": true,
},
"typescript.tsdk": "ui/node_modules/typescript/lib",
"[go]": {
"editor.defaultFormatter": "golang.go"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
}

View File

@@ -1,8 +1 @@
## v0.0.3
- 解决一些 bug
- 添加 README.md
## v0.0.1
- Initial release
A full changelog of past releases is available on [GitHub Releases](https://github.com/usual2970/certimate/releases) page.

View File

@@ -10,7 +10,7 @@ RUN \
npm run build
FROM golang:1.22-alpine AS builder
FROM golang:1.23-alpine AS builder
WORKDIR /app

208
README.md
View File

@@ -1,182 +1,110 @@
[中文](README.md) | [English](README_EN.md)
<h1 align="center">🔒 Certimate</h1>
# 🔒Certimate
<div align="center">
做个人产品或在小企业负责运维的同学,需要管理多个域名,要给域名申请证书。但手动申请证书有以下缺点:
[![Stars](https://img.shields.io/github/stars/usual2970/certimate?style=flat)](https://github.com/usual2970/certimate)
[![Forks](https://img.shields.io/github/forks/usual2970/certimate?style=flat)](https://github.com/usual2970/certimate)
[![Docker Pulls](https://img.shields.io/docker/pulls/usual2970/certimate?style=flat)](https://hub.docker.com/r/usual2970/certimate)
[![Release](https://img.shields.io/github/v/release/usual2970/certimate?sort=semver)](https://github.com/usual2970/certimate/releases)
[![License](https://img.shields.io/github/license/usual2970/certimate)](https://mit-license.org/)
1. 😱 麻烦:申请、部署证书虽不困难,但也挺麻烦的,尤其是维护多个域名的时候。
2. 😭 易忘:当前免费证书有效期仅 90 天,这就要求定期操作,增加工作量的同时,也很容易忘掉,导致网站无法访问。
</div>
Certimate 就是为了解决上述问题而产生的,它具有以下特点:
<div align="center">
1. 操作简单:自动申请、部署、续期 SSL 证书,全程无需人工干预。
2. 支持私有部署部署方法简单只需下载二进制文件执行即可。二进制文件、Docker 镜像全部用 Github Actions 生成,过程透明,可自行审计。
3. 数据安全:由于是私有部署,所有数据均存储在本地,不会保存在服务商的服务器,确保数据的安全性。
中文 [English](README_EN.md)
相关文章:
</div>
- [V0.2.0-第一个不向后兼容的版本](https://docs.certimate.me/blog/v0.2.0)
- [Why Certimate?](https://docs.certimate.me/blog/why-certimate)
- [域名变量及部署授权组介绍](https://docs.certimate.me/blog/multi-deployer)
---
Certimate 旨在为用户提供一个安全、简便的 SSL 证书管理解决方案。使用文档请访问 [https://docs.certimate.me](https://docs.certimate.me)
## 🚩 项目简介
## 一、安装
做个人产品或者在中小企业里负责运维的同学,会遇到要管理多个域名的情况,需要给域名申请证书。但是手动申请证书有以下缺点:
安装 Certimate 非常简单,你可以选择以下方式之一进行安装:
- 😱 麻烦:申请证书并部署到服务的流程虽不复杂,但也挺麻烦的,犹其是你有多个域名需要维护的时候。
- 😭 易忘:另外当前免费证书的有效期只有 90 天,这就要求你定期的操作,增加了工作量的同时,你也很容易忘掉续期,从而导致网站访问不了。
### 1. 二进制文件
Certimate 就是为了解决上述问题而产生的,它具有以下优势:
你可以直接从[Releases 页](https://github.com/usual2970/certimate/releases)下载预先编译好的二进制文件,解压后执行:
- **本地部署**:一键安装,只需要下载二进制文件,然后直接运行即可。同时也支持 Docker 部署、源代码部署等方式。​
- **数据安全**:由于是私有部署,所有数据均存储在自己的服务器上,不会经过第三方,确保数据的隐私和安全。​
- **操作简单**:简单配置即可轻松申请 SSL 证书并部署到指定的目标上,在证书即将过期前自动续期,从申请证书到使用证书完全自动化,无需人工操作。​
Certimate 旨在为用户提供一个安全、简便的 SSL 证书管理解决方案。
## 💡 功能特性
- 灵活的工作流编排方式,证书从申请到部署完全自动化;
- 支持单域名、多域名、泛域名证书,可选 RSA、ECC 签名算法;
- 支持 PEM、PFX、JKS 等多种格式输出证书;
- 支持 20+ 域名托管商如阿里云、腾讯云、Cloudflare 等,[点此查看](https://docs.certimate.me/docs/reference/providers#supported-dns-providers)完整提供商清单);
- 支持 60+ 部署目标(如 Kubernetes、CDN、WAF、负载均衡等[点此查看](https://docs.certimate.me/docs/reference/providers#supported-host-providers)完整提供商清单);
- 支持邮件、钉钉、飞书、企业微信、Webhook 等多种通知渠道;
- 支持 Let's Encrypt、ZeroSSL、Google Trust Services 等多种 ACME 证书颁发机构;
- 更多特性等待探索。
## ⏱️ 快速启动
**5 分钟部署 Certimate**
以二进制部署为例,从 [GitHub Releases](https://github.com/usual2970/certimate/releases) 页面下载预先编译好的二进制可执行文件压缩包,解压缩后在终端中执行:
```bash
./certimate serve
```
或运行以下命令自动给 Certimate 自身添加证书
浏览器中访问 `http://127.0.0.1:8090`
```bash
./certimate serve 你的域名
```
初始的管理员账号及密码:
> [!NOTE]
> MacOS 在执行二进制文件时会提示无法打开“Certimate”因为 Apple 无法检查其是否包含恶意软件。可在“系统设置 > 隐私与安全性 > 安全性”中点击“仍然允许”,然后再次尝试执行二进制文件。
- 账号:`admin@certimate.fun`
- 密码:`1234567890`
### 2. Docker 安装
即刻使用 Certimate。
```bash
如何使用 Docker 或其他部署方式请参考文档。
mkdir -p ~/.certimate && cd ~/.certimate && curl -O https://raw.githubusercontent.com/usual2970/certimate/refs/heads/main/docker/docker-compose.yml && docker compose up -d
## 📄 使用手册
```
请访问文档站 [docs.certimate.me](https://docs.certimate.me/) 以阅读使用手册。
### 3. 源代码安装
相关文章:
```bash
git clone EMAIL:usual2970/certimate.git
cd certimate
make local.run
```
- [v0.3.0:第二个不向后兼容的大版本](https://docs.certimate.me/blog/v0.3.0)
- [v0.2.0:第一个不向后兼容的大版本](https://docs.certimate.me/blog/v0.2.0)
- [Why Certimate?](https://docs.certimate.me/blog/why-certimate)
## 二、使用
## ⭐ 运行界面
执行完上述安装操作后,在浏览器中访问 `http://127.0.0.1:8090` 即可访问 Certimate 管理页面。
[![Screenshot](https://i.imgur.com/4DAUKEE.gif)](https://www.bilibili.com/video/BV1xockeZEm2)
```bash
用户名admin@certimate.fun
密码1234567890
```
## 🤝 参与贡献
![usage.gif](https://i.imgur.com/zpCoLVM.gif)
## 三、支持的服务商列表
| 服务商 | 支持申请证书 | 支持部署证书 | 备注 |
| :--------: | :----------: | :----------: | ------------------------------------------------------------ |
| 阿里云 | √ | √ | 可签发在阿里云注册的域名;可部署到阿里云 OSS、CDN |
| 腾讯云 | √ | √ | 可签发在腾讯云注册的域名;可部署到腾讯云 CDN、COS |
| 华为云 | √ | √ | 可签发在华为云注册的域名;可部署到华为云 CDN |
| 七牛云 | | √ | 可部署到七牛云 CDN |
| AWS | √ | | 可签发在 AWS Route53 托管的域名 |
| CloudFlare | √ | | 可签发在 CloudFlare 注册的域名CloudFlare 服务自带 SSL 证书 |
| GoDaddy | √ | | 可签发在 GoDaddy 注册的域名 |
| Namesilo | √ | | 可签发在 Namesilo 注册的域名 |
| PowerDNS | √ | | 可签发在 PowerDNS 托管的域名 |
| HTTP 请求 | √ | | 可签发允许通过 HTTP 请求修改 DNS 的域名 |
| 本地部署 | | √ | 可部署到本地服务器 |
| SSH | | √ | 可部署到 SSH 服务器 |
| Webhook | | √ | 可部署时回调到 Webhook |
| Kubernetes | | √ | 可部署到 Kubernetes Secret |
## 四、系统截图
![login](https://i.imgur.com/SYjjbql.jpeg)
![dashboard](https://i.imgur.com/WMVbBId.jpeg)
![domains](https://i.imgur.com/8wit3ZA.jpeg)
![accesses](https://i.imgur.com/EWtOoJ0.jpeg)
![history](https://i.imgur.com/aaPtSW7.jpeg)
## 五、概念
Certimate 的工作流程如下:
- 用户通过 Certimate 管理页面填写申请证书的信息包括域名、DNS 服务商的授权信息、以及要部署到的服务商的授权信息。
- Certimate 向证书厂商的 API 发起申请请求,获取 SSL 证书。
- Certimate 存储证书信息,包括证书内容、私钥、证书有效期等,并在证书即将过期时自动续期。
- Certimate 向服务商的 API 发起部署请求,将证书部署到服务商的服务器上。
这就涉及域名、DNS 服务商的授权信息、部署服务商的授权信息等。
### 1. 域名
就是要申请证书的域名。
### 2. DNS 服务商授权信息
给域名申请证书需要证明域名是你的,所以我们手动申请证书的时候一般需要在域名服务商的控制台解析记录中添加一个 TXT 域名解析记录。
Certimate 会自动添加一个 TXT 域名解析记录,你只需要在 Certimate 后台中填写你的域名服务商的授权信息即可。
比如你在阿里云购买的域名,授权信息如下:
```bash
accessKeyId: your-access-key-id
accessKeySecret: your-access-key-secret
```
在腾讯云购买的域名,授权信息如下:
```bash
secretId: your-secret-id
secretKey: your-secret-key
```
注意,此授权信息需具有访问域名及 DNS 解析的管理权限,具体的权限清单请参阅各服务商自己的技术文档。
### 3. 部署服务商授权信息
Certimate 申请证书后,会自动将证书部署到你指定的目标上,比如阿里云 CDNCertimate 会根据你填写的授权信息及域名找到对应的 CDN 服务,并将证书部署到对应的 CDN 服务上。
部署服务商授权信息和 DNS 服务商授权信息基本一致,区别在于 DNS 服务商授权信息用于证明域名是你的,部署服务商授权信息用于提供证书部署的授权信息。
注意,此授权信息需具有访问部署目标服务的相关管理权限,具体的权限清单请参阅各服务商自己的技术文档。
## 六、常见问题
Q: 提供 SaaS 服务吗?
> A: 不提供,目前仅支持 self-hosted私有部署
Q: 数据安全?
> A: 由于仅支持私有部署,各种数据都保存在用户的服务器上。另外 Certimate 源码也开源,二进制包及 Docker 镜像打包过程全部使用 Github Actions 进行,过程透明可见,可自行审计。
Q: 自动续期证书?
> A: 已经申请的证书会在**过期前 10 天**自动续期。每天会检查一次证书是否快要过期,快要过期时会自动重新申请证书并部署到目标服务上。
## 七、贡献
Certimate 是一个免费且开源的项目,采用 [MIT 开源协议](LICENSE.md)。你可以使用它做任何你想做的事,甚至把它当作一个付费服务提供给用户。
Certimate 是一个免费且开源的项目,采用 [MIT License](./LICENSE.md)。你可以使用它做任何你想做的事,甚至把它当作一个付费服务提供给用户。
你可以通过以下方式来支持 Certimate 的开发:
- 提交代码:如果你发现了 Bug 或有新的功能需求,而你又有相关经验,可以[提交代码](CONTRIBUTING.md)给我们。
- 提交 Issue功能建议或者 Bug 可以[提交 Issue](https://github.com/usual2970/certimate/issues) 给我们。
支持更多服务商、UI 的优化改进、Bug 修复、文档完善等,欢迎大家提交 PR
支持更多提供商、UI 的优化改进、Bug 修复、文档完善等,欢迎大家参与贡献
## 八、加入社区
## ⛔ 免责声明
- [Telegram-a new era of messaging](https://t.me/+ZXphsppxUg41YmVl)
Certimate 基于 [MIT License](https://opensource.org/licenses/MIT) 发布,完全免费提供,旨在“按现状”供用户使用。作者及贡献者不对使用本软件所产生的任何直接或间接后果承担责任,包括但不限于性能下降、数据丢失、服务中断、或任何其他类型的损害。
**无任何保证**:本软件不提供任何明示或暗示的保证,包括但不限于对特定用途的适用性、无侵权性、商用性及可靠性的保证。
**用户责任**:使用本软件即表示您理解并同意承担由此产生的一切风险及责任。
## 🌐 加入社群
- [Telegram](https://t.me/+ZXphsppxUg41YmVl)
- 微信群聊(超 200 人需邀请入群,可先加作者好友)
<img src="https://i.imgur.com/8xwsLTA.png" width="400"/>
<img src="https://i.imgur.com/8xwsLTA.png" width="240"/>
## 九、Star 趋势图
## 🚀 Star 趋势图
[![Stargazers over time](https://starchart.cc/usual2970/certimate.svg?variant=adaptive)](https://starchart.cc/usual2970/certimate)

View File

@@ -1,181 +1,108 @@
[中文](README.md) | [English](README_EN.md)
<h1 align="center">🔒 Certimate</h1>
# 🔒Certimate
<div align="center">
[![Stars](https://img.shields.io/github/stars/usual2970/certimate?style=flat)](https://github.com/usual2970/certimate)
[![Forks](https://img.shields.io/github/forks/usual2970/certimate?style=flat)](https://github.com/usual2970/certimate)
[![Docker Pulls](https://img.shields.io/docker/pulls/usual2970/certimate?style=flat)](https://hub.docker.com/r/usual2970/certimate)
[![Release](https://img.shields.io/github/v/release/usual2970/certimate?style=flat&sort=semver)](https://github.com/usual2970/certimate/releases)
[![License](https://img.shields.io/github/license/usual2970/certimate?style=flat)](https://mit-license.org/)
</div>
<div align="center">
[中文](README.md) English
</div>
---
## 🚩 Introduction
For individuals managing personal projects or those responsible for IT operations in small businesses who need to manage multiple domain names, applying for certificates manually comes with several drawbacks:
1. 😱Troublesome: Applying for and deploying certificates isnt difficult, but it can be quite a hassle, especially when managing multiple domains.
2. 😭Easily forgotten: The current free certificate has a validity period of only 90 days, requiring regular renewal operations. This increases the workload and makes it easy to forget, which can result in the website becoming inaccessible.
- 😱 Troublesome: Applying for and deploying certificates isnt difficult, but it can be quite a hassle, especially when managing multiple domains.
- 😭 Easily forgotten: The current free certificate has a validity period of only 90 days, requiring regular renewal operations. This increases the workload and makes it easy to forget, which can result in the website becoming inaccessible.
Certimate was created to solve the above-mentioned issues and has the following features:
Certimate was created to solve the above-mentioned issues and has the following advantages:
1. Simple operation: Automatically apply, deploy, and renew SSL certificates without any manual intervention.
2. Support for self-hosted deployment: The deployment method is simple; you only need to download the binary file and execute it. Both the binary files and Docker images are generated using GitHub Actions, ensuring a transparent process that can be audited independently.
3. Data security: Since it is a self-hosted deployment, all data is stored locally and will not be saved on the service providers servers, ensuring the security of the data.
- **Local Deployment**: Simply to install, download the binary and run it directly. Supports Docker deployment and source code deployment for added flexibility.
- **Data Security**: With private deployment, all data is stored on your own servers, ensuring it never resides on third-party systems and maintaining full control over your data.
- **Easy Operation**: Effortlessly apply and deploy SSL certificates with minimal configuration. The system automatically renews certificates before expiration, providing a fully automated workflow, no manual intervention required.
Related articles:
Certimate aims to provide users with a secure and user-friendly SSL certificate management solution.
- [Why Certimate?](https://docs.certimate.me/blog/why-certimate)
- [Introduction to Domain Variables and Deployment Authorization Groups](https://docs.certimate.me/blog/multi-deployer)
## 💡 Features
Certimate aims to provide users with a secure and user-friendly SSL certificate management solution. For usage documentation, please visit [https://docs.certimate.me](https://docs.certimate.me).
- Flexible workflow orchestration, fully automation from certificate application to deployment;
- Supports single-domain, multi-domain, wildcard certificates, with options for RSA or ECC.
- Supports various certificate formats such as PEM, PFX, JKS.
- Supports more than 20+ domain registrars (e.g., Alibaba Cloud, Tencent Cloud, Cloudflare, etc. [Check out this link](https://docs.certimate.me/en/docs/reference/providers#supported-dns-providers));
- Supports more than 60+ deployment targets (e.g., Kubernetes, CDN, WAF, load balancers, etc. [Check out this link](https://docs.certimate.me/en/docs/reference/providers#supported-host-providers));
- Supports multiple notification channels including email, DingTalk, Feishu, WeCom, Webhook, and more;
- Supports multiple ACME CAs including Let's Encrypt, ZeroSSL, Google Trust Services, and more;
- More features waiting to be discovered.
## Installation
## ⏱️ Fast Track
Installing Certimate is very simple, you can choose one of the following methods for installation:
**Deploy Certimate in 5 minutes!**
### 1. Binary File
You can download the precompiled binary files directly from the [Releases page](https://github.com/usual2970/certimate/releases), and after extracting them, execute:
Download the archived package of precompiled binary files directly from [GitHub Releases](https://github.com/usual2970/certimate/releases), extract and then execute:
```bash
./certimate serve
```
Or run the following command to automatically add a certificate to Certimate itself.
Visit `http://127.0.0.1:8090` in your browser.
```bash
./certimate serve yourDomain
```
Default administrator account:
> [!NOTE]
> When executing the binary file on macOS, you may see a prompt saying: “Cannot open certimate because Apple cannot check it for malicious software.” You can go to System Preferences > Security & Privacy > General, then click “Allow Anyway,” and try executing the binary file again.
- Username: `admin@certimate.fun`
- Password: `1234567890`
### 2. Docker Installation
Work with Certimate right now. Or read other content in the documentation to learn more.
```bash
## 📄 Documentation
mkdir -p ~/.certimate && cd ~/.certimate && curl -O https://raw.githubusercontent.com/usual2970/certimate/refs/heads/main/docker/docker-compose.yml && docker compose up -d
Please visit the documentation site [docs.certimate.me](https://docs.certimate.me/en/).
```
Related articles:
### 3. Source Code Installation
- [v0.3.0:第二个不向后兼容的大版本](https://docs.certimate.me/blog/v0.3.0)
- [v0.2.0:第一个不向后兼容的大版本](https://docs.certimate.me/blog/v0.2.0)
- [Why Certimate?](https://docs.certimate.me/blog/why-certimate)
```bash
git clone EMAIL:usual2970/certimate.git
cd certimate
make local.run
```
## ⭐ Screenshot
## Usage
[![Screenshot](https://i.imgur.com/4DAUKEE.gif)](https://www.youtube.com/watch?v=am_yzdfyNOE)
After completing the installation steps above, you can access the Certimate management page by visiting http://127.0.0.1:8090 in your browser.
## 🤝 Contributing
```bash
usernameadmin@certimate.fun
password1234567890
```
![usage.gif](https://i.imgur.com/zpCoLVM.gif)
## List of Supported Providers
| Provider | Registration | Deployment | Remarks |
| :-----------: | :----------: | :--------: | ------------------------------------------------------------------------------------------- |
| Alibaba Cloud | √ | √ | Supports domains registered on Alibaba Cloud; supports deployment to Alibaba Cloud OSS, CDN |
| Tencent Cloud | √ | √ | Supports domains registered on Tencent Cloud; supports deployment to Tencent Cloud CDN, COS |
| Huawei Cloud | √ | √ | Supports domains registered on Huawei Cloud; supports deployment to Huawei Cloud CDN |
| Qiniu Cloud | | √ | Supports deployment to Qiniu Cloud CDN |
| AWS | √ | | Supports domains managed on AWS Route53 |
| CloudFlare | √ | | Supports domains registered on CloudFlare; CloudFlare services come with SSL certificates |
| GoDaddy | √ | | Supports domains registered on GoDaddy |
| Namesilo | √ | | Supports domains registered on Namesilo |
| PowerDNS | √ | | Supports domains managed on PowerDNS |
| HTTP Request | √ | | Supports domains which allow managing DNS by HTTP request |
| Local Deploy | | √ | Supports deployment to local servers |
| SSH | | √ | Supports deployment to SSH servers |
| Webhook | | √ | Supports callback to Webhook |
| Kubernetes | | √ | Supports deployment to Kubernetes Secret |
## Screenshots
![login](https://i.imgur.com/SYjjbql.jpeg)
![dashboard](https://i.imgur.com/WMVbBId.jpeg)
![domains](https://i.imgur.com/8wit3ZA.jpeg)
![accesses](https://i.imgur.com/EWtOoJ0.jpeg)
![history](https://i.imgur.com/aaPtSW7.jpeg)
## Concepts
The workflow of Certimate is as follows:
- Users fill in the certificate application information on the Certimate management page, including domain name, authorization information for the DNS provider, and authorization information for the service provider to deploy to.
- Certimate sends a request to the certificate vendor's API to apply for an SSL certificate.
- Certimate stores the certificate information, including the certificate content, private key, validity period, etc., and automatically renews the certificate when it is about to expire.
- Certimate sends a deployment request to the service provider's API to deploy the certificate to the service provider's servers.
This involves authorization information for the domain, DNS provider, and deployment service provider.
### 1. Domain
It involves the domain name for which the certificate is being requested.
### 2. Authorization Information for the DNS Provider
To apply for a certificate for a domain, you need to prove that the domain belongs to you. Therefore, when manually applying for a certificate, you typically need to add a TXT record to the DNS records in the domain provider's control panel.
Certimate will automatically add a TXT record for you; you only need to fill in the authorization information for your DNS provider in the Certimate backend.
For example, if you purchased the domain from Alibaba Cloud, the authorization information would be as follows:
```bash
accessKeyId: your-access-key-id
accessKeySecret: your-access-key-secret
```
If you purchased the domain from Tencent Cloud, the authorization information would be as follows:
```bash
secretId: your-secret-id
secretKey: your-secret-key
```
Notes: This authorization information requires relevant administration permissions for accessing the DNS services. Please refer to the documentations of each service provider for the specific permissions list.
### 3. Authorization Information for the Deployment Service Provider
After Certimate applies for the certificate, it will automatically deploy the certificate to your specified target, such as Alibaba Cloud CDN. At this point, you need to fill in the authorization information for Alibaba Cloud. Certimate will use the authorization information and domain name you provided to locate the corresponding CDN service and deploy the certificate to that service.
The authorization information for the deployment service provider is the same as that for the DNS provider, with the distinction that the DNS provider's authorization information is used to prove that the domain belongs to you, while the deployment service provider's authorization information is used to provide authorization for the certificate deployment.
Notes: This authorization information requires relevant administration permissions to access the target deployment services. Please refer to the documentations of each service provider for the specific permissions list.
## FAQ
Q: Do you provide SaaS services?
> A: No, we do not provide that. Currently, we only support self-hosted.
Q: Data Security?
> A: Since only self-hosted is supported, all data is stored on the users server. Additionally, the source code of Certimate is open-source, and the packaging process for binary files and Docker images is entirely done using GitHub Actions. This process is transparent and visible, allowing for independent auditing.
Q: Automatic Certificate Renewal?
> A: Certificates that have already been issued will be automatically renewed **10 days before expiration**. The system checks once a day to see if any certificates are nearing expiration, and if so, it will automatically reapply for the certificate and deploy it to the target service.
## Contributing
Certimate is a free and open-source project, licensed under the [MIT License](LICENSE.md). You can use it for anything you want, even offering it as a paid service to users.
Certimate is a free and open-source project, licensed under the [MIT License](./LICENSE.md). You can use it for anything you want, even offering it as a paid service to users.
You can support the development of Certimate in the following ways:
- **Submit Code**: If you find a bug or have new feature requests, and you have relevant experience, [you can submit code to us](CONTRIBUTING_EN.md).
- **Submit an Issue**: For feature suggestions or bugs, you can [submit an issue](https://github.com/usual2970/certimate/issues) to us.
Support for more service providers, UI enhancements, bug fixes, and documentation improvements are all welcome. We encourage everyone to submit pull requests (PRs).
Support for more service providers, UI enhancements, bug fixes, and documentation improvements are all welcome. We encourage everyone to contribute.
## Join the Community
## ⛔ Disclaimer
- [Telegram-a new era of messaging](https://t.me/+ZXphsppxUg41YmVl)
This software is provided under the [MIT License](https://opensource.org/licenses/MIT) and distributed “as-is” without any warranty of any kind. The authors and contributors are not responsible for any damages or losses resulting from the use or inability to use this software, including but not limited to data loss, business interruption, or any other potential harm.
**No Warranties**: This software comes without any express or implied warranties, including but not limited to implied warranties of merchantability, fitness for a particular purpose, and non-infringement.
**User Responsibilities**: By using this software, you agree to take full responsibility for any outcomes resulting from its use.
## 🌐 Join the Community
- [Telegram](https://t.me/+ZXphsppxUg41YmVl)
- Wechat Group
<img src="https://i.imgur.com/zSHEoIm.png" width="400"/>
<img src="https://i.imgur.com/zSHEoIm.png" width="240"/>
## Star History
## 🚀 Star History
[![Stargazers over time](https://starchart.cc/usual2970/certimate.svg?variant=adaptive)](https://starchart.cc/usual2970/certimate)

263
go.mod
View File

@@ -1,174 +1,223 @@
module github.com/usual2970/certimate
go 1.22.0
go 1.23.0
toolchain go1.23.2
require (
github.com/alibabacloud-go/cas-20200407/v3 v3.0.1
github.com/alibabacloud-go/cdn-20180510/v5 v5.0.0
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10
github.com/alibabacloud-go/tea v1.2.2
github.com/alibabacloud-go/tea-utils/v2 v2.0.6
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azcertificates v0.9.0
github.com/G-Core/gcorelabscdn-go v1.0.26
github.com/alibabacloud-go/alb-20200616/v2 v2.2.8
github.com/alibabacloud-go/cas-20200407/v3 v3.0.4
github.com/alibabacloud-go/cdn-20180510/v5 v5.2.2
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.2
github.com/alibabacloud-go/esa-20240910/v2 v2.22.1
github.com/alibabacloud-go/live-20161101 v1.1.1
github.com/alibabacloud-go/nlb-20220430/v2 v2.0.3
github.com/alibabacloud-go/slb-20140515/v4 v4.0.10
github.com/alibabacloud-go/tea v1.3.2
github.com/alibabacloud-go/vod-20170321/v4 v4.6.1
github.com/alibabacloud-go/waf-openapi-20211001/v5 v5.0.5
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
github.com/go-acme/lego/v4 v4.19.2
github.com/gojek/heimdall/v7 v7.0.3
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.114
github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61
github.com/nikoksr/notify v1.0.0
github.com/pkg/sftp v1.13.6
github.com/pocketbase/dbx v1.10.1
github.com/pocketbase/pocketbase v0.22.18
github.com/qiniu/go-sdk/v7 v7.22.0
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.0.1017
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1017
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.0.992
golang.org/x/crypto v0.27.0
k8s.io/apimachinery v0.31.1
k8s.io/client-go v0.31.1
github.com/aws/aws-sdk-go-v2/service/acm v1.31.1
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.45.1
github.com/baidubce/bce-sdk-go v0.9.218
github.com/byteplus-sdk/byteplus-sdk-golang v1.0.41
github.com/go-acme/lego/v4 v4.22.2
github.com/go-resty/resty/v2 v2.16.5
github.com/go-viper/mapstructure/v2 v2.2.1
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.138
github.com/jdcloud-api/jdcloud-sdk-go v1.62.0
github.com/nikoksr/notify v1.3.0
github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0
github.com/pkg/sftp v1.13.7
github.com/pocketbase/dbx v1.11.0
github.com/pocketbase/pocketbase v0.25.9
github.com/povsister/scp v0.0.0-20240802064259-28781e87b246
github.com/qiniu/go-sdk/v7 v7.25.2
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.0.1115
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb v1.0.1115
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1115
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/live v1.0.1115
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.0.1115
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo v1.0.1115
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vod v1.0.1102
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/waf v1.0.1099
github.com/ucloud/ucloud-sdk-go v0.22.31
github.com/volcengine/ve-tos-golang-sdk/v2 v2.7.9
github.com/volcengine/volc-sdk-golang v1.0.197
github.com/volcengine/volcengine-go-sdk v1.0.184
gitlab.ecloud.com/ecloud/ecloudsdkclouddns v1.0.1
gitlab.ecloud.com/ecloud/ecloudsdkcore v1.0.0
golang.org/x/crypto v0.36.0
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394
k8s.io/api v0.32.2
k8s.io/apimachinery v0.32.2
k8s.io/client-go v0.32.2
software.sslmate.com/src/go-pkcs12 v0.5.0
)
require (
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 // indirect
github.com/alibabacloud-go/alibabacloud-gateway-fc-util v0.0.7 // indirect
github.com/alibabacloud-go/fc-20230330/v4 v4.1.7 // indirect
github.com/alibabacloud-go/fc-open-20210406 v1.1.14 // indirect
github.com/alibabacloud-go/fc-open-20210406/v2 v2.0.12 // indirect
github.com/alibabacloud-go/openplatform-20191219/v2 v2.0.1 // indirect
github.com/alibabacloud-go/tea-fileform v1.1.1 // indirect
github.com/alibabacloud-go/tea-oss-sdk v1.1.3 // indirect
github.com/alibabacloud-go/tea-oss-utils v1.1.0 // indirect
github.com/aws/aws-sdk-go-v2/service/route53 v1.43.2 // indirect
github.com/alibabacloud-go/tea-utils/v2 v2.0.7 // indirect
github.com/avast/retry-go v3.0.0+incompatible // indirect
github.com/aws/aws-sdk-go-v2/service/route53 v1.48.1 // indirect
github.com/blinkbean/dingtalk v1.1.3 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/emicklei/go-restful/v3 v3.12.1 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-lark/lark v1.14.1 // indirect
github.com/go-lark/lark v1.15.1 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.4 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.16.0 // indirect
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect
github.com/gofrs/uuid v4.4.0+incompatible // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/gnostic-models v0.6.9 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/imdario/mergo v0.3.6 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
github.com/nrdcg/mailinabox v0.2.0 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/qiniu/dyn v1.3.0 // indirect
github.com/qiniu/x v1.10.5 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/scf v1.0.1115 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.mongodb.org/mongo-driver v1.12.0 // indirect
go.mongodb.org/mongo-driver v1.17.2 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ns1/ns1-go.v2 v2.13.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/api v0.31.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 // indirect
k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.5.0 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)
require (
github.com/AlecAivazis/survey/v2 v2.3.7 // indirect
github.com/BurntSushi/toml v1.4.0 // indirect
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect
github.com/alibabacloud-go/dcdn-20180115/v3 v3.4.2
github.com/alibabacloud-go/dcdn-20180115/v3 v3.5.0
github.com/alibabacloud-go/debug v1.0.1 // indirect
github.com/alibabacloud-go/endpoint-util v1.1.0 // indirect
github.com/alibabacloud-go/openapi-util v0.1.0 // indirect
github.com/alibabacloud-go/endpoint-util v1.1.1 // indirect
github.com/alibabacloud-go/openapi-util v0.1.1 // indirect
github.com/alibabacloud-go/tea-utils v1.4.5 // indirect
github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.63.15 // indirect
github.com/aliyun/credentials-go v1.3.10 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.63.83 // indirect
github.com/aliyun/credentials-go v1.4.3 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/aws/aws-sdk-go-v2 v1.30.5 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4 // indirect
github.com/aws/aws-sdk-go-v2/config v1.27.33 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.32 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13 // indirect
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.8 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.17 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.19 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.17 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.22.7 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.7 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.30.7 // indirect
github.com/aws/smithy-go v1.20.4 // indirect
github.com/aws/aws-sdk-go-v2 v1.36.3
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.8 // indirect
github.com/aws/aws-sdk-go-v2/config v1.29.5
github.com/aws/aws-sdk-go-v2/credentials v1.17.58
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.28 // indirect
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.58 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.32 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.6.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.13 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.13 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.75.3 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.24.15 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.14 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.14 // indirect
github.com/aws/smithy-go v1.22.2 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/clbanning/mxj/v2 v2.5.6 // indirect
github.com/cloudflare/cloudflare-go v0.104.0 // indirect
github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/cloudflare/cloudflare-go v0.114.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/disintegration/imaging v1.6.2 // indirect
github.com/domodwyer/mailyak/v3 v3.6.2 // indirect
github.com/domodwyer/mailyak/v3 v3.6.2
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fatih/color v1.17.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.4 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/ganigeorgiev/fexpr v0.4.1 // indirect
github.com/go-jose/go-jose/v4 v4.0.4 // indirect
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/gojek/valkyrie v0.0.0-20180215180059-6aee720afcdf // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/goccy/go-json v0.10.4 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/gax-go/v2 v2.13.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/google/uuid v1.6.0
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kr/fs v0.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.22 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/miekg/dns v1.1.62 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/nrdcg/namesilo v0.2.1 // indirect
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/errors v0.9.1
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/spf13/cobra v1.8.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/stretchr/testify v1.9.0 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1002 // indirect
github.com/stretchr/testify v1.10.0 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1084 // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
go.opencensus.io v0.24.0 // indirect
gocloud.dev v0.37.0 // indirect
golang.org/x/image v0.18.0 // indirect
golang.org/x/mod v0.21.0 // indirect
golang.org/x/net v0.29.0 // indirect
golang.org/x/oauth2 v0.23.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/term v0.24.0 // indirect
golang.org/x/text v0.18.0 // indirect
golang.org/x/time v0.6.0 // indirect
golang.org/x/tools v0.25.0 // indirect
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect
google.golang.org/api v0.197.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
google.golang.org/grpc v1.66.1 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gocloud.dev v0.40.0 // indirect
golang.org/x/image v0.24.0 // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/net v0.37.0 // indirect
golang.org/x/oauth2 v0.26.0 // indirect
golang.org/x/sync v0.12.0
golang.org/x/sys v0.31.0 // indirect
golang.org/x/term v0.30.0 // indirect
golang.org/x/text v0.23.0 // indirect
golang.org/x/time v0.9.0
golang.org/x/tools v0.31.0 // indirect
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
google.golang.org/api v0.220.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250207221924-e9438ea467c6 // indirect
google.golang.org/grpc v1.70.0 // indirect
google.golang.org/protobuf v1.36.5 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/gc/v3 v3.0.0-20240722195230-4a140ff9c08e // indirect
modernc.org/libc v1.55.3 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.8.0 // indirect
modernc.org/sqlite v1.31.1 // indirect
modernc.org/strutil v1.2.0 // indirect
modernc.org/token v1.1.0 // indirect
modernc.org/libc v1.61.13 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.8.2 // indirect
modernc.org/sqlite v1.35.0 // indirect
)
replace gitlab.ecloud.com/ecloud/ecloudsdkcore v1.0.0 => ./internal/pkg/vendors/cmcc-sdk/ecloudsdkcore@v1.0.0
replace gitlab.ecloud.com/ecloud/ecloudsdkclouddns v1.0.1 => ./internal/pkg/vendors/cmcc-sdk/ecloudsdkclouddns@v1.0.1

1274
go.sum
View File

File diff suppressed because it is too large Load Diff

32
internal/app/app.go Normal file
View File

@@ -0,0 +1,32 @@
package app
import (
"log/slog"
"sync"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
var instance core.App
var intanceOnce sync.Once
func GetApp() core.App {
intanceOnce.Do(func() {
instance = pocketbase.NewWithConfig(pocketbase.Config{
HideStartBanner: true,
})
})
return instance
}
func GetDB() dbx.Builder {
return GetApp().DB()
}
func GetLogger() *slog.Logger {
return GetApp().Logger()
}

View File

@@ -2,17 +2,24 @@ package app
import (
"sync"
"time"
"github.com/pocketbase/pocketbase/tools/cron"
)
var schedulerOnce sync.Once
var scheduler *cron.Cron
var schedulerOnce sync.Once
func GetScheduler() *cron.Cron {
scheduler = GetApp().Cron()
schedulerOnce.Do(func() {
scheduler = cron.New()
location, err := time.LoadLocation("Local")
if err == nil {
scheduler.Stop()
scheduler.SetTimezone(location)
scheduler.Start()
}
})
return scheduler

View File

@@ -0,0 +1,38 @@
package applicant
const (
sslProviderLetsEncrypt = "letsencrypt"
sslProviderLetsEncryptStaging = "letsencrypt_staging"
sslProviderZeroSSL = "zerossl"
sslProviderGoogleTrustServices = "gts"
)
const defaultSSLProvider = sslProviderLetsEncrypt
const (
letsencryptUrl = "https://acme-v02.api.letsencrypt.org/directory"
letsencryptStagingUrl = "https://acme-staging-v02.api.letsencrypt.org/directory"
zerosslUrl = "https://acme.zerossl.com/v2/DV90"
gtsUrl = "https://dv.acme-v02.api.pki.goog/directory"
)
var sslProviderUrls = map[string]string{
sslProviderLetsEncrypt: letsencryptUrl,
sslProviderLetsEncryptStaging: letsencryptStagingUrl,
sslProviderZeroSSL: zerosslUrl,
sslProviderGoogleTrustServices: gtsUrl,
}
type acmeSSLProviderConfig struct {
Config acmeSSLProviderConfigContent `json:"config"`
Provider string `json:"provider"`
}
type acmeSSLProviderConfigContent struct {
ZeroSSL acmeSSLProviderEabConfig `json:"zerossl"`
GoogleTrustServices acmeSSLProviderEabConfig `json:"gts"`
}
type acmeSSLProviderEabConfig struct {
EabHmacKey string `json:"eabHmacKey"`
EabKid string `json:"eabKid"`
}

View File

@@ -0,0 +1,140 @@
package applicant
import (
"context"
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"fmt"
"github.com/go-acme/lego/v4/lego"
"github.com/go-acme/lego/v4/registration"
"golang.org/x/sync/singleflight"
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/pkg/utils/certs"
"github.com/usual2970/certimate/internal/repository"
)
type acmeUser struct {
CA string
Email string
Registration *registration.Resource
privkey string
}
func newAcmeUser(ca, email string) (*acmeUser, error) {
repo := repository.NewAcmeAccountRepository()
applyUser := &acmeUser{
CA: ca,
Email: email,
}
acmeAccount, err := repo.GetByCAAndEmail(ca, email)
if err != nil {
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, err
}
keyPEM, err := certs.ConvertECPrivateKeyToPEM(key)
if err != nil {
return nil, err
}
applyUser.privkey = keyPEM
return applyUser, nil
}
applyUser.Registration = acmeAccount.Resource
applyUser.privkey = acmeAccount.Key
return applyUser, nil
}
func (u *acmeUser) GetEmail() string {
return u.Email
}
func (u acmeUser) GetRegistration() *registration.Resource {
return u.Registration
}
func (u *acmeUser) GetPrivateKey() crypto.PrivateKey {
rs, _ := certs.ParseECPrivateKeyFromPEM(u.privkey)
return rs
}
func (u *acmeUser) hasRegistration() bool {
return u.Registration != nil
}
func (u *acmeUser) getPrivateKeyPEM() string {
return u.privkey
}
type acmeAccountRepository interface {
GetByCAAndEmail(ca, email string) (*domain.AcmeAccount, error)
Save(ca, email, key string, resource *registration.Resource) error
}
var registerGroup singleflight.Group
func registerAcmeUserWithSingleFlight(client *lego.Client, sslProviderConfig *acmeSSLProviderConfig, user *acmeUser) (*registration.Resource, error) {
resp, err, _ := registerGroup.Do(fmt.Sprintf("register_acme_user_%s_%s", sslProviderConfig.Provider, user.GetEmail()), func() (interface{}, error) {
return registerAcmeUser(client, sslProviderConfig, user)
})
if err != nil {
return nil, err
}
return resp.(*registration.Resource), nil
}
func registerAcmeUser(client *lego.Client, sslProviderConfig *acmeSSLProviderConfig, user *acmeUser) (*registration.Resource, error) {
var reg *registration.Resource
var err error
switch sslProviderConfig.Provider {
case sslProviderZeroSSL:
reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
TermsOfServiceAgreed: true,
Kid: sslProviderConfig.Config.ZeroSSL.EabKid,
HmacEncoded: sslProviderConfig.Config.ZeroSSL.EabHmacKey,
})
case sslProviderGoogleTrustServices:
reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
TermsOfServiceAgreed: true,
Kid: sslProviderConfig.Config.GoogleTrustServices.EabKid,
HmacEncoded: sslProviderConfig.Config.GoogleTrustServices.EabHmacKey,
})
case sslProviderLetsEncrypt, sslProviderLetsEncryptStaging:
reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
default:
err = fmt.Errorf("unsupported ssl provider: %s", sslProviderConfig.Provider)
}
if err != nil {
return nil, err
}
repo := repository.NewAcmeAccountRepository()
resp, err := repo.GetByCAAndEmail(sslProviderConfig.Provider, user.GetEmail())
if err == nil {
user.privkey = resp.Key
return resp.Resource, nil
}
if _, err := repo.Save(context.Background(), &domain.AcmeAccount{
CA: sslProviderConfig.Provider,
Email: user.GetEmail(),
Key: user.getPrivateKeyPEM(),
Resource: reg,
}); err != nil {
return nil, fmt.Errorf("failed to save registration: %w", err)
}
return reg, nil
}

View File

@@ -1,36 +0,0 @@
package applicant
import (
"encoding/json"
"fmt"
"os"
"github.com/go-acme/lego/v4/providers/dns/alidns"
"github.com/usual2970/certimate/internal/domain"
)
type aliyun struct {
option *ApplyOption
}
func NewAliyun(option *ApplyOption) Applicant {
return &aliyun{
option: option,
}
}
func (a *aliyun) Apply() (*Certificate, error) {
access := &domain.AliyunAccess{}
json.Unmarshal([]byte(a.option.Access), access)
os.Setenv("ALICLOUD_ACCESS_KEY", access.AccessKeyId)
os.Setenv("ALICLOUD_SECRET_KEY", access.AccessKeySecret)
os.Setenv("ALICLOUD_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", a.option.Timeout))
dnsProvider, err := alidns.NewDNSProvider()
if err != nil {
return nil, err
}
return apply(a.option, dnsProvider)
}

View File

@@ -1,299 +1,233 @@
package applicant
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"errors"
"context"
"encoding/json"
"fmt"
"os"
"strconv"
"strings"
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/utils/app"
"sync"
"github.com/go-acme/lego/v4/certcrypto"
"github.com/go-acme/lego/v4/certificate"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/lego"
"github.com/go-acme/lego/v4/registration"
"github.com/pocketbase/pocketbase/models"
"golang.org/x/exp/slices"
"golang.org/x/time/rate"
"github.com/usual2970/certimate/internal/domain"
uslices "github.com/usual2970/certimate/internal/pkg/utils/slices"
"github.com/usual2970/certimate/internal/repository"
)
const (
configTypeAliyun = "aliyun"
configTypeTencent = "tencent"
configTypeHuaweiCloud = "huaweicloud"
configTypeAws = "aws"
configTypeCloudflare = "cloudflare"
configTypeNamesilo = "namesilo"
configTypeGodaddy = "godaddy"
configTypePdns = "pdns"
configTypeHttpreq = "httpreq"
)
const defaultSSLProvider = "letsencrypt"
const (
sslProviderLetsencrypt = "letsencrypt"
sslProviderZeroSSL = "zerossl"
)
const (
zerosslUrl = "https://acme.zerossl.com/v2/DV90"
letsencryptUrl = "https://acme-v02.api.letsencrypt.org/directory"
)
var sslProviderUrls = map[string]string{
sslProviderLetsencrypt: letsencryptUrl,
sslProviderZeroSSL: zerosslUrl,
}
const defaultEmail = "536464346@qq.com"
const defaultTimeout = 60
type Certificate struct {
CertUrl string `json:"certUrl"`
CertStableUrl string `json:"certStableUrl"`
PrivateKey string `json:"privateKey"`
Certificate string `json:"certificate"`
IssuerCertificate string `json:"issuerCertificate"`
Csr string `json:"csr"`
}
type ApplyOption struct {
Email string `json:"email"`
Domain string `json:"domain"`
Access string `json:"access"`
KeyAlgorithm string `json:"keyAlgorithm"`
Nameservers string `json:"nameservers"`
Timeout int64 `json:"timeout"`
DisableFollowCNAME bool `json:"disableFollowCNAME"`
}
type ApplyUser struct {
Email string
Registration *registration.Resource
key crypto.PrivateKey
}
func (u *ApplyUser) GetEmail() string {
return u.Email
}
func (u ApplyUser) GetRegistration() *registration.Resource {
return u.Registration
}
func (u *ApplyUser) GetPrivateKey() crypto.PrivateKey {
return u.key
type ApplyCertResult struct {
CertificateFullChain string
IssuerCertificate string
PrivateKey string
ACMEAccountUrl string
ACMECertUrl string
ACMECertStableUrl string
CSR string
}
type Applicant interface {
Apply() (*Certificate, error)
Apply() (*ApplyCertResult, error)
}
func Get(record *models.Record) (Applicant, error) {
if record.GetString("applyConfig") == "" {
return nil, errors.New("applyConfig is empty")
type applicantOptions struct {
Domains []string
ContactEmail string
Provider domain.ApplyDNSProviderType
ProviderAccessConfig map[string]any
ProviderApplyConfig map[string]any
KeyAlgorithm string
Nameservers []string
DnsPropagationTimeout int32
DnsTTL int32
DisableFollowCNAME bool
ReplacedARIAcctId string
ReplacedARICertId string
}
func NewWithApplyNode(node *domain.WorkflowNode) (Applicant, error) {
if node.Type != domain.WorkflowNodeTypeApply {
return nil, fmt.Errorf("node type is not apply")
}
applyConfig := &domain.ApplyConfig{}
record.UnmarshalJSONField("applyConfig", applyConfig)
nodeConfig := node.GetConfigForApply()
options := &applicantOptions{
Domains: uslices.Filter(strings.Split(nodeConfig.Domains, ";"), func(s string) bool { return s != "" }),
ContactEmail: nodeConfig.ContactEmail,
Provider: domain.ApplyDNSProviderType(nodeConfig.Provider),
ProviderApplyConfig: nodeConfig.ProviderConfig,
KeyAlgorithm: nodeConfig.KeyAlgorithm,
Nameservers: uslices.Filter(strings.Split(nodeConfig.Nameservers, ";"), func(s string) bool { return s != "" }),
DnsPropagationTimeout: nodeConfig.DnsPropagationTimeout,
DnsTTL: nodeConfig.DnsTTL,
DisableFollowCNAME: nodeConfig.DisableFollowCNAME,
}
access, err := app.GetApp().Dao().FindRecordById("access", applyConfig.Access)
accessRepo := repository.NewAccessRepository()
if access, err := accessRepo.GetById(context.Background(), nodeConfig.ProviderAccessId); err != nil {
return nil, fmt.Errorf("failed to get access #%s record: %w", nodeConfig.ProviderAccessId, err)
} else {
accessConfig, err := access.UnmarshalConfigToMap()
if err != nil {
return nil, fmt.Errorf("failed to unmarshal access config: %w", err)
}
options.ProviderAccessConfig = accessConfig
}
certRepo := repository.NewCertificateRepository()
lastCertificate, _ := certRepo.GetByWorkflowNodeId(context.Background(), node.Id)
if lastCertificate != nil {
newCertSan := slices.Clone(options.Domains)
oldCertSan := strings.Split(lastCertificate.SubjectAltNames, ";")
slices.Sort(newCertSan)
slices.Sort(oldCertSan)
if slices.Equal(newCertSan, oldCertSan) {
lastCertX509, _ := certcrypto.ParsePEMCertificate([]byte(lastCertificate.Certificate))
if lastCertX509 != nil {
replacedARICertId, _ := certificate.MakeARICertID(lastCertX509)
options.ReplacedARIAcctId = lastCertificate.ACMEAccountUrl
options.ReplacedARICertId = replacedARICertId
}
}
}
applicant, err := createApplicant(options)
if err != nil {
return nil, fmt.Errorf("access record not found: %w", err)
return nil, err
}
if applyConfig.Email == "" {
applyConfig.Email = defaultEmail
}
if applyConfig.Timeout == 0 {
applyConfig.Timeout = defaultTimeout
}
option := &ApplyOption{
Email: applyConfig.Email,
Domain: record.GetString("domain"),
Access: access.GetString("config"),
KeyAlgorithm: applyConfig.KeyAlgorithm,
Nameservers: applyConfig.Nameservers,
Timeout: applyConfig.Timeout,
DisableFollowCNAME: applyConfig.DisableFollowCNAME,
}
switch access.GetString("configType") {
case configTypeAliyun:
return NewAliyun(option), nil
case configTypeTencent:
return NewTencent(option), nil
case configTypeHuaweiCloud:
return NewHuaweiCloud(option), nil
case configTypeAws:
return NewAws(option), nil
case configTypeCloudflare:
return NewCloudflare(option), nil
case configTypeNamesilo:
return NewNamesilo(option), nil
case configTypeGodaddy:
return NewGodaddy(option), nil
case configTypePdns:
return NewPdns(option), nil
case configTypeHttpreq:
return NewHttpreq(option), nil
default:
return nil, errors.New("unknown config type")
}
return &proxyApplicant{
applicant: applicant,
options: options,
}, nil
}
type SSLProviderConfig struct {
Config SSLProviderConfigContent `json:"config"`
Provider string `json:"provider"`
}
func apply(challengeProvider challenge.Provider, options *applicantOptions) (*ApplyCertResult, error) {
settingsRepo := repository.NewSettingsRepository()
settings, _ := settingsRepo.GetByName(context.Background(), "sslProvider")
type SSLProviderConfigContent struct {
Zerossl struct {
EabHmacKey string `json:"eabHmacKey"`
EabKid string `json:"eabKid"`
}
}
func apply(option *ApplyOption, provider challenge.Provider) (*Certificate, error) {
record, _ := app.GetApp().Dao().FindFirstRecordByFilter("settings", "name='ssl-provider'")
sslProvider := &SSLProviderConfig{
Config: SSLProviderConfigContent{},
sslProviderConfig := &acmeSSLProviderConfig{
Config: acmeSSLProviderConfigContent{},
Provider: defaultSSLProvider,
}
if record != nil {
if err := record.UnmarshalJSONField("content", sslProvider); err != nil {
if settings != nil {
if err := json.Unmarshal([]byte(settings.Content), sslProviderConfig); err != nil {
return nil, err
}
}
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if sslProviderConfig.Provider == "" {
sslProviderConfig.Provider = defaultSSLProvider
}
acmeUser, err := newAcmeUser(sslProviderConfig.Provider, options.ContactEmail)
if err != nil {
return nil, err
}
// Some unified lego environment variables are configured here.
// link: https://github.com/go-acme/lego/issues/1867
os.Setenv("LEGO_DISABLE_CNAME_SUPPORT", strconv.FormatBool(option.DisableFollowCNAME))
os.Setenv("LEGO_DISABLE_CNAME_SUPPORT", strconv.FormatBool(options.DisableFollowCNAME))
myUser := ApplyUser{
Email: option.Email,
key: privateKey,
}
// Create an ACME client config
config := lego.NewConfig(acmeUser)
config.CADirURL = sslProviderUrls[sslProviderConfig.Provider]
config.Certificate.KeyType = parseKeyAlgorithm(domain.CertificateKeyAlgorithmType(options.KeyAlgorithm))
config := lego.NewConfig(&myUser)
// This CA URL is configured for a local dev instance of Boulder running in Docker in a VM.
config.CADirURL = sslProviderUrls[sslProvider.Provider]
config.Certificate.KeyType = parseKeyAlgorithm(option.KeyAlgorithm)
// A client facilitates communication with the CA server.
// Create an ACME client
client, err := lego.NewClient(config)
if err != nil {
return nil, err
}
// Set the DNS01 challenge provider
challengeOptions := make([]dns01.ChallengeOption, 0)
nameservers := parseNameservers(option.Nameservers)
if len(nameservers) > 0 {
challengeOptions = append(challengeOptions, dns01.AddRecursiveNameservers(nameservers))
if len(options.Nameservers) > 0 {
challengeOptions = append(challengeOptions, dns01.AddRecursiveNameservers(dns01.ParseNameservers(options.Nameservers)))
challengeOptions = append(challengeOptions, dns01.DisableAuthoritativeNssPropagationRequirement())
}
client.Challenge.SetDNS01Provider(challengeProvider, challengeOptions...)
// New users need to register first
if !acmeUser.hasRegistration() {
reg, err := registerAcmeUserWithSingleFlight(client, sslProviderConfig, acmeUser)
if err != nil {
return nil, fmt.Errorf("failed to register: %w", err)
}
acmeUser.Registration = reg
}
client.Challenge.SetDNS01Provider(provider, challengeOptions...)
// New users will need to register
reg, err := getReg(client, sslProvider)
if err != nil {
return nil, fmt.Errorf("failed to register: %w", err)
}
myUser.Registration = reg
domains := strings.Split(option.Domain, ";")
request := certificate.ObtainRequest{
Domains: domains,
// Obtain a certificate
certRequest := certificate.ObtainRequest{
Domains: options.Domains,
Bundle: true,
}
certificates, err := client.Certificate.Obtain(request)
if options.ReplacedARICertId != "" && options.ReplacedARIAcctId != acmeUser.Registration.URI {
certRequest.ReplacesCertID = options.ReplacedARICertId
}
certResource, err := client.Certificate.Obtain(certRequest)
if err != nil {
return nil, err
}
return &Certificate{
CertUrl: certificates.CertURL,
CertStableUrl: certificates.CertStableURL,
PrivateKey: string(certificates.PrivateKey),
Certificate: string(certificates.Certificate),
IssuerCertificate: string(certificates.IssuerCertificate),
Csr: string(certificates.CSR),
return &ApplyCertResult{
CertificateFullChain: strings.TrimSpace(string(certResource.Certificate)),
IssuerCertificate: strings.TrimSpace(string(certResource.IssuerCertificate)),
PrivateKey: strings.TrimSpace(string(certResource.PrivateKey)),
ACMEAccountUrl: acmeUser.Registration.URI,
ACMECertUrl: certResource.CertURL,
ACMECertStableUrl: certResource.CertStableURL,
CSR: strings.TrimSpace(string(certResource.CSR)),
}, nil
}
func getReg(client *lego.Client, sslProvider *SSLProviderConfig) (*registration.Resource, error) {
var reg *registration.Resource
var err error
switch sslProvider.Provider {
case sslProviderZeroSSL:
reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
TermsOfServiceAgreed: true,
Kid: sslProvider.Config.Zerossl.EabKid,
HmacEncoded: sslProvider.Config.Zerossl.EabHmacKey,
})
case sslProviderLetsencrypt:
reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
default:
err = errors.New("unknown ssl provider")
}
if err != nil {
return nil, err
}
return reg, nil
}
func parseNameservers(ns string) []string {
nameservers := make([]string, 0)
lines := strings.Split(ns, ";")
for _, line := range lines {
line = strings.TrimSpace(line)
if line == "" {
continue
}
nameservers = append(nameservers, line)
}
return nameservers
}
func parseKeyAlgorithm(algo string) certcrypto.KeyType {
func parseKeyAlgorithm(algo domain.CertificateKeyAlgorithmType) certcrypto.KeyType {
switch algo {
case "RSA2048":
case domain.CertificateKeyAlgorithmTypeRSA2048:
return certcrypto.RSA2048
case "RSA3072":
case domain.CertificateKeyAlgorithmTypeRSA3072:
return certcrypto.RSA3072
case "RSA4096":
case domain.CertificateKeyAlgorithmTypeRSA4096:
return certcrypto.RSA4096
case "RSA8192":
case domain.CertificateKeyAlgorithmTypeRSA8192:
return certcrypto.RSA8192
case "EC256":
case domain.CertificateKeyAlgorithmTypeEC256:
return certcrypto.EC256
case "EC384":
case domain.CertificateKeyAlgorithmTypeEC384:
return certcrypto.EC384
default:
return certcrypto.RSA2048
}
return certcrypto.RSA2048
}
// TODO: 暂时使用代理模式以兼容之前版本代码,后续重新实现此处逻辑
type proxyApplicant struct {
applicant challenge.Provider
options *applicantOptions
}
var limiters sync.Map
const (
limitBurst = 300
limitRate float64 = float64(1) / float64(36)
)
func getLimiter(key string) *rate.Limiter {
limiter, _ := limiters.LoadOrStore(key, rate.NewLimiter(rate.Limit(limitRate), 300))
return limiter.(*rate.Limiter)
}
func (d *proxyApplicant) Apply() (*ApplyCertResult, error) {
limiter := getLimiter(fmt.Sprintf("apply_%s", d.options.ContactEmail))
limiter.Wait(context.Background())
return apply(d.applicant, d.options)
}

View File

@@ -0,0 +1,44 @@
package applicant_test
import (
"testing"
"time"
"golang.org/x/time/rate"
)
func TestRateLimit(t *testing.T) {
tests := []struct {
name string
burst int
rate rate.Limit
}{
{
name: "test1",
burst: 300,
rate: rate.Limit(float64(1) / float64(20)),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
rl := rate.NewLimiter(tt.rate, tt.burst)
if rl.Burst() != tt.burst {
t.Errorf("Burst() = %v, want %v", rl.Burst(), tt.burst)
}
if rl.Limit() != tt.rate {
t.Errorf("Limit() = %v, want %v", rl.Limit(), tt.rate)
}
t.Log("consume all tokens at once", rl.AllowN(time.Now(), tt.burst))
t.Log("consume more", rl.Allow())
time.Sleep(time.Second * 5)
t.Log("consume after 5 seconds", rl.Allow())
time.Sleep(time.Second * 20)
t.Log("consume after 20 seconds", rl.Allow())
})
}
}

View File

@@ -1,39 +0,0 @@
package applicant
import (
"encoding/json"
"fmt"
"os"
"github.com/go-acme/lego/v4/providers/dns/route53"
"github.com/usual2970/certimate/internal/domain"
)
type aws struct {
option *ApplyOption
}
func NewAws(option *ApplyOption) Applicant {
return &aws{
option: option,
}
}
func (t *aws) Apply() (*Certificate, error) {
access := &domain.AwsAccess{}
json.Unmarshal([]byte(t.option.Access), access)
os.Setenv("AWS_REGION", access.Region)
os.Setenv("AWS_ACCESS_KEY_ID", access.AccessKeyId)
os.Setenv("AWS_SECRET_ACCESS_KEY", access.SecretAccessKey)
os.Setenv("AWS_HOSTED_ZONE_ID", access.HostedZoneId)
os.Setenv("AWS_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", t.option.Timeout))
dnsProvider, err := route53.NewDNSProvider()
if err != nil {
return nil, err
}
return apply(t.option, dnsProvider)
}

View File

@@ -1,36 +0,0 @@
package applicant
import (
"encoding/json"
"fmt"
"os"
cf "github.com/go-acme/lego/v4/providers/dns/cloudflare"
"github.com/usual2970/certimate/internal/domain"
)
type cloudflare struct {
option *ApplyOption
}
func NewCloudflare(option *ApplyOption) Applicant {
return &cloudflare{
option: option,
}
}
func (c *cloudflare) Apply() (*Certificate, error) {
access := &domain.CloudflareAccess{}
json.Unmarshal([]byte(c.option.Access), access)
os.Setenv("CLOUDFLARE_DNS_API_TOKEN", access.DnsApiToken)
os.Setenv("CLOUDFLARE_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", c.option.Timeout))
provider, err := cf.NewDNSProvider()
if err != nil {
return nil, err
}
return apply(c.option, provider)
}

View File

@@ -1,37 +0,0 @@
package applicant
import (
"encoding/json"
"fmt"
"os"
godaddyProvider "github.com/go-acme/lego/v4/providers/dns/godaddy"
"github.com/usual2970/certimate/internal/domain"
)
type godaddy struct {
option *ApplyOption
}
func NewGodaddy(option *ApplyOption) Applicant {
return &godaddy{
option: option,
}
}
func (a *godaddy) Apply() (*Certificate, error) {
access := &domain.GodaddyAccess{}
json.Unmarshal([]byte(a.option.Access), access)
os.Setenv("GODADDY_API_KEY", access.ApiKey)
os.Setenv("GODADDY_API_SECRET", access.ApiSecret)
os.Setenv("GODADDY_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", a.option.Timeout))
dnsProvider, err := godaddyProvider.NewDNSProvider()
if err != nil {
return nil, err
}
return apply(a.option, dnsProvider)
}

View File

@@ -1,38 +0,0 @@
package applicant
import (
"encoding/json"
"fmt"
"os"
"github.com/go-acme/lego/v4/providers/dns/httpreq"
"github.com/usual2970/certimate/internal/domain"
)
type httpReq struct {
option *ApplyOption
}
func NewHttpreq(option *ApplyOption) Applicant {
return &httpReq{
option: option,
}
}
func (a *httpReq) Apply() (*Certificate, error) {
access := &domain.HttpreqAccess{}
json.Unmarshal([]byte(a.option.Access), access)
os.Setenv("HTTPREQ_ENDPOINT", access.Endpoint)
os.Setenv("HTTPREQ_MODE", access.Mode)
os.Setenv("HTTPREQ_USERNAME", access.Username)
os.Setenv("HTTPREQ_PASSWORD", access.Password)
os.Setenv("HTTPREQ_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", a.option.Timeout))
dnsProvider, err := httpreq.NewDNSProvider()
if err != nil {
return nil, err
}
return apply(a.option, dnsProvider)
}

View File

@@ -1,43 +0,0 @@
package applicant
import (
"encoding/json"
"fmt"
"os"
huaweicloudProvider "github.com/go-acme/lego/v4/providers/dns/huaweicloud"
"github.com/usual2970/certimate/internal/domain"
)
type huaweicloud struct {
option *ApplyOption
}
func NewHuaweiCloud(option *ApplyOption) Applicant {
return &huaweicloud{
option: option,
}
}
func (t *huaweicloud) Apply() (*Certificate, error) {
access := &domain.HuaweiCloudAccess{}
json.Unmarshal([]byte(t.option.Access), access)
region := access.Region
if region == "" {
region = "cn-north-1"
}
os.Setenv("HUAWEICLOUD_REGION", region) // 华为云的 SDK 要求必须传一个区域,实际上 DNS-01 流程里用不到,但不传会报错
os.Setenv("HUAWEICLOUD_ACCESS_KEY_ID", access.AccessKeyId)
os.Setenv("HUAWEICLOUD_SECRET_ACCESS_KEY", access.SecretAccessKey)
os.Setenv("HUAWEICLOUD_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", t.option.Timeout))
dnsProvider, err := huaweicloudProvider.NewDNSProvider()
if err != nil {
return nil, err
}
return apply(t.option, dnsProvider)
}

View File

@@ -1,36 +0,0 @@
package applicant
import (
"encoding/json"
"fmt"
"os"
namesiloProvider "github.com/go-acme/lego/v4/providers/dns/namesilo"
"github.com/usual2970/certimate/internal/domain"
)
type namesilo struct {
option *ApplyOption
}
func NewNamesilo(option *ApplyOption) Applicant {
return &namesilo{
option: option,
}
}
func (a *namesilo) Apply() (*Certificate, error) {
access := &domain.NameSiloAccess{}
json.Unmarshal([]byte(a.option.Access), access)
os.Setenv("NAMESILO_API_KEY", access.ApiKey)
os.Setenv("NAMESILO_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", a.option.Timeout))
dnsProvider, err := namesiloProvider.NewDNSProvider()
if err != nil {
return nil, err
}
return apply(a.option, dnsProvider)
}

View File

@@ -1,36 +0,0 @@
package applicant
import (
"encoding/json"
"fmt"
"os"
"github.com/go-acme/lego/v4/providers/dns/pdns"
"github.com/usual2970/certimate/internal/domain"
)
type powerdns struct {
option *ApplyOption
}
func NewPdns(option *ApplyOption) Applicant {
return &powerdns{
option: option,
}
}
func (a *powerdns) Apply() (*Certificate, error) {
access := &domain.PdnsAccess{}
json.Unmarshal([]byte(a.option.Access), access)
os.Setenv("PDNS_API_URL", access.ApiUrl)
os.Setenv("PDNS_API_KEY", access.ApiKey)
os.Setenv("PDNS_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", a.option.Timeout))
dnsProvider, err := pdns.NewDNSProvider()
if err != nil {
return nil, err
}
return apply(a.option, dnsProvider)
}

View File

@@ -0,0 +1,413 @@
package applicant
import (
"fmt"
"github.com/go-acme/lego/v4/challenge"
"github.com/usual2970/certimate/internal/domain"
pACMEHttpReq "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/acmehttpreq"
pAliyun "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/aliyun"
pAWSRoute53 "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/aws-route53"
pAzureDNS "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/azure-dns"
pBaiduCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/baiducloud"
pCloudflare "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/cloudflare"
pClouDNS "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/cloudns"
pCMCCCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/cmcccloud"
pDNSLA "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/dnsla"
pGcore "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/gcore"
pGname "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname"
pGoDaddy "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/godaddy"
pHuaweiCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/huaweicloud"
pJDCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/jdcloud"
pNamecheap "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/namecheap"
pNameDotCom "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/namedotcom"
pNameSilo "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/namesilo"
pNS1 "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/ns1"
pPowerDNS "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/powerdns"
pRainYun "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/rainyun"
pTencentCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/tencentcloud"
pVolcEngine "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/volcengine"
pWestcn "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/westcn"
"github.com/usual2970/certimate/internal/pkg/utils/maps"
)
func createApplicant(options *applicantOptions) (challenge.Provider, error) {
/*
注意:如果追加新的常量值,请保持以 ASCII 排序。
NOTICE: If you add new constant, please keep ASCII order.
*/
switch options.Provider {
case domain.ApplyDNSProviderTypeACMEHttpReq:
{
access := domain.AccessConfigForACMEHttpReq{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
applicant, err := pACMEHttpReq.NewChallengeProvider(&pACMEHttpReq.ChallengeProviderConfig{
Endpoint: access.Endpoint,
Mode: access.Mode,
Username: access.Username,
Password: access.Password,
DnsPropagationTimeout: options.DnsPropagationTimeout,
})
return applicant, err
}
case domain.ApplyDNSProviderTypeAliyun, domain.ApplyDNSProviderTypeAliyunDNS:
{
access := domain.AccessConfigForAliyun{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
applicant, err := pAliyun.NewChallengeProvider(&pAliyun.ChallengeProviderConfig{
AccessKeyId: access.AccessKeyId,
AccessKeySecret: access.AccessKeySecret,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
case domain.ApplyDNSProviderTypeAWS, domain.ApplyDNSProviderTypeAWSRoute53:
{
access := domain.AccessConfigForAWS{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
applicant, err := pAWSRoute53.NewChallengeProvider(&pAWSRoute53.ChallengeProviderConfig{
AccessKeyId: access.AccessKeyId,
SecretAccessKey: access.SecretAccessKey,
Region: maps.GetValueAsString(options.ProviderApplyConfig, "region"),
HostedZoneId: maps.GetValueAsString(options.ProviderApplyConfig, "hostedZoneId"),
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
case domain.ApplyDNSProviderTypeAzure, domain.ApplyDNSProviderTypeAzureDNS:
{
access := domain.AccessConfigForAzure{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
applicant, err := pAzureDNS.NewChallengeProvider(&pAzureDNS.ChallengeProviderConfig{
TenantId: access.TenantId,
ClientId: access.ClientId,
ClientSecret: access.ClientSecret,
CloudName: access.CloudName,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
case domain.ApplyDNSProviderTypeBaiduCloud, domain.ApplyDNSProviderTypeBaiduCloudDNS:
{
access := domain.AccessConfigForBaiduCloud{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
applicant, err := pBaiduCloud.NewChallengeProvider(&pBaiduCloud.ChallengeProviderConfig{
AccessKeyId: access.AccessKeyId,
SecretAccessKey: access.SecretAccessKey,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
case domain.ApplyDNSProviderTypeCloudflare:
{
access := domain.AccessConfigForCloudflare{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
applicant, err := pCloudflare.NewChallengeProvider(&pCloudflare.ChallengeProviderConfig{
DnsApiToken: access.DnsApiToken,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
case domain.ApplyDNSProviderTypeClouDNS:
{
access := domain.AccessConfigForClouDNS{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
applicant, err := pClouDNS.NewChallengeProvider(&pClouDNS.ChallengeProviderConfig{
AuthId: access.AuthId,
AuthPassword: access.AuthPassword,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
case domain.ApplyDNSProviderTypeCMCCCloud:
{
access := domain.AccessConfigForCMCCCloud{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
applicant, err := pCMCCCloud.NewChallengeProvider(&pCMCCCloud.ChallengeProviderConfig{
AccessKeyId: access.AccessKeyId,
AccessKeySecret: access.AccessKeySecret,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
case domain.ApplyDNSProviderTypeDNSLA:
{
access := domain.AccessConfigForDNSLA{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
applicant, err := pDNSLA.NewChallengeProvider(&pDNSLA.ChallengeProviderConfig{
ApiId: access.ApiId,
ApiSecret: access.ApiSecret,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
case domain.ApplyDNSProviderTypeGcore:
{
access := domain.AccessConfigForGcore{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
applicant, err := pGcore.NewChallengeProvider(&pGcore.ChallengeProviderConfig{
ApiToken: access.ApiToken,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
case domain.ApplyDNSProviderTypeGname:
{
access := domain.AccessConfigForGname{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
applicant, err := pGname.NewChallengeProvider(&pGname.ChallengeProviderConfig{
AppId: access.AppId,
AppKey: access.AppKey,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
case domain.ApplyDNSProviderTypeGoDaddy:
{
access := domain.AccessConfigForGoDaddy{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
applicant, err := pGoDaddy.NewChallengeProvider(&pGoDaddy.ChallengeProviderConfig{
ApiKey: access.ApiKey,
ApiSecret: access.ApiSecret,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
case domain.ApplyDNSProviderTypeHuaweiCloud, domain.ApplyDNSProviderTypeHuaweiCloudDNS:
{
access := domain.AccessConfigForHuaweiCloud{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
applicant, err := pHuaweiCloud.NewChallengeProvider(&pHuaweiCloud.ChallengeProviderConfig{
AccessKeyId: access.AccessKeyId,
SecretAccessKey: access.SecretAccessKey,
Region: maps.GetValueAsString(options.ProviderApplyConfig, "region"),
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
case domain.ApplyDNSProviderTypeJDCloud, domain.ApplyDNSProviderTypeJDCloudDNS:
{
access := domain.AccessConfigForJDCloud{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
applicant, err := pJDCloud.NewChallengeProvider(&pJDCloud.ChallengeProviderConfig{
AccessKeyId: access.AccessKeyId,
AccessKeySecret: access.AccessKeySecret,
RegionId: maps.GetValueAsString(options.ProviderApplyConfig, "region_id"),
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
case domain.ApplyDNSProviderTypeNamecheap:
{
access := domain.AccessConfigForNamecheap{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
applicant, err := pNamecheap.NewChallengeProvider(&pNamecheap.ChallengeProviderConfig{
Username: access.Username,
ApiKey: access.ApiKey,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
case domain.ApplyDNSProviderTypeNameDotCom:
{
access := domain.AccessConfigForNameDotCom{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
applicant, err := pNameDotCom.NewChallengeProvider(&pNameDotCom.ChallengeProviderConfig{
Username: access.Username,
ApiToken: access.ApiToken,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
case domain.ApplyDNSProviderTypeNameSilo:
{
access := domain.AccessConfigForNameSilo{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
applicant, err := pNameSilo.NewChallengeProvider(&pNameSilo.ChallengeProviderConfig{
ApiKey: access.ApiKey,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
case domain.ApplyDNSProviderTypeNS1:
{
access := domain.AccessConfigForNS1{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
applicant, err := pNS1.NewChallengeProvider(&pNS1.ChallengeProviderConfig{
ApiKey: access.ApiKey,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
case domain.ApplyDNSProviderTypePowerDNS:
{
access := domain.AccessConfigForPowerDNS{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
applicant, err := pPowerDNS.NewChallengeProvider(&pPowerDNS.ChallengeProviderConfig{
ApiUrl: access.ApiUrl,
ApiKey: access.ApiKey,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
case domain.ApplyDNSProviderTypeRainYun:
{
access := domain.AccessConfigForRainYun{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
applicant, err := pRainYun.NewChallengeProvider(&pRainYun.ChallengeProviderConfig{
ApiKey: access.ApiKey,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
case domain.ApplyDNSProviderTypeTencentCloud, domain.ApplyDNSProviderTypeTencentCloudDNS:
{
access := domain.AccessConfigForTencentCloud{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
applicant, err := pTencentCloud.NewChallengeProvider(&pTencentCloud.ChallengeProviderConfig{
SecretId: access.SecretId,
SecretKey: access.SecretKey,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
case domain.ApplyDNSProviderTypeVolcEngine, domain.ApplyDNSProviderTypeVolcEngineDNS:
{
access := domain.AccessConfigForVolcEngine{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
applicant, err := pVolcEngine.NewChallengeProvider(&pVolcEngine.ChallengeProviderConfig{
AccessKeyId: access.AccessKeyId,
SecretAccessKey: access.SecretAccessKey,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
case domain.ApplyDNSProviderTypeWestcn:
{
access := domain.AccessConfigForWestcn{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
applicant, err := pWestcn.NewChallengeProvider(&pWestcn.ChallengeProviderConfig{
Username: access.Username,
ApiPassword: access.ApiPassword,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return applicant, err
}
}
return nil, fmt.Errorf("unsupported applicant provider: %s", string(options.Provider))
}

View File

@@ -1,37 +0,0 @@
package applicant
import (
"encoding/json"
"fmt"
"os"
"github.com/go-acme/lego/v4/providers/dns/tencentcloud"
"github.com/usual2970/certimate/internal/domain"
)
type tencent struct {
option *ApplyOption
}
func NewTencent(option *ApplyOption) Applicant {
return &tencent{
option: option,
}
}
func (t *tencent) Apply() (*Certificate, error) {
access := &domain.TencentAccess{}
json.Unmarshal([]byte(t.option.Access), access)
os.Setenv("TENCENTCLOUD_SECRET_ID", access.SecretId)
os.Setenv("TENCENTCLOUD_SECRET_KEY", access.SecretKey)
os.Setenv("TENCENTCLOUD_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", t.option.Timeout))
dnsProvider, err := tencentcloud.NewDNSProvider()
if err != nil {
return nil, err
}
return apply(t.option, dnsProvider)
}

View File

@@ -0,0 +1,256 @@
package certificate
import (
"archive/zip"
"bytes"
"context"
"encoding/json"
"fmt"
"strconv"
"strings"
"time"
"github.com/go-acme/lego/v4/certcrypto"
"github.com/usual2970/certimate/internal/app"
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/domain/dtos"
"github.com/usual2970/certimate/internal/notify"
"github.com/usual2970/certimate/internal/pkg/utils/certs"
"github.com/usual2970/certimate/internal/repository"
)
const (
defaultExpireSubject = "有 ${COUNT} 张证书即将过期"
defaultExpireMessage = "有 ${COUNT} 张证书即将过期,域名分别为 ${DOMAINS},请保持关注!"
)
type certificateRepository interface {
ListExpireSoon(ctx context.Context) ([]*domain.Certificate, error)
GetById(ctx context.Context, id string) (*domain.Certificate, error)
}
type CertificateService struct {
certRepo certificateRepository
}
func NewCertificateService(certRepo certificateRepository) *CertificateService {
return &CertificateService{
certRepo: certRepo,
}
}
func (s *CertificateService) InitSchedule(ctx context.Context) error {
app.GetScheduler().MustAdd("certificateExpireSoonNotify", "0 0 * * *", func() {
certificates, err := s.certRepo.ListExpireSoon(context.Background())
if err != nil {
app.GetLogger().Error("failed to get certificates which expire soon", "err", err)
return
}
notification := buildExpireSoonNotification(certificates)
if notification == nil {
return
}
if err := notify.SendToAllChannels(notification.Subject, notification.Message); err != nil {
app.GetLogger().Error("failed to send notification", "err", err)
}
})
return nil
}
func (s *CertificateService) ArchiveFile(ctx context.Context, req *dtos.CertificateArchiveFileReq) (*dtos.CertificateArchiveFileResp, error) {
certificate, err := s.certRepo.GetById(ctx, req.CertificateId)
if err != nil {
return nil, err
}
var buf bytes.Buffer
zipWriter := zip.NewWriter(&buf)
defer zipWriter.Close()
resp := &dtos.CertificateArchiveFileResp{
FileFormat: "zip",
}
switch strings.ToUpper(req.Format) {
case "", "PEM":
{
certWriter, err := zipWriter.Create("certbundle.pem")
if err != nil {
return nil, err
}
_, err = certWriter.Write([]byte(certificate.Certificate))
if err != nil {
return nil, err
}
keyWriter, err := zipWriter.Create("privkey.pem")
if err != nil {
return nil, err
}
_, err = keyWriter.Write([]byte(certificate.PrivateKey))
if err != nil {
return nil, err
}
err = zipWriter.Close()
if err != nil {
return nil, err
}
resp.FileBytes = buf.Bytes()
return resp, nil
}
case "PFX":
{
const pfxPassword = "certimate"
certPFX, err := certs.TransformCertificateFromPEMToPFX(certificate.Certificate, certificate.PrivateKey, pfxPassword)
if err != nil {
return nil, err
}
certWriter, err := zipWriter.Create("cert.pfx")
if err != nil {
return nil, err
}
_, err = certWriter.Write(certPFX)
if err != nil {
return nil, err
}
keyWriter, err := zipWriter.Create("pfx-password.txt")
if err != nil {
return nil, err
}
_, err = keyWriter.Write([]byte(pfxPassword))
if err != nil {
return nil, err
}
err = zipWriter.Close()
if err != nil {
return nil, err
}
resp.FileBytes = buf.Bytes()
return resp, nil
}
case "JKS":
{
const jksPassword = "certimate"
certJKS, err := certs.TransformCertificateFromPEMToJKS(certificate.Certificate, certificate.PrivateKey, jksPassword, jksPassword, jksPassword)
if err != nil {
return nil, err
}
certWriter, err := zipWriter.Create("cert.jks")
if err != nil {
return nil, err
}
_, err = certWriter.Write(certJKS)
if err != nil {
return nil, err
}
keyWriter, err := zipWriter.Create("jks-password.txt")
if err != nil {
return nil, err
}
_, err = keyWriter.Write([]byte(jksPassword))
if err != nil {
return nil, err
}
err = zipWriter.Close()
if err != nil {
return nil, err
}
resp.FileBytes = buf.Bytes()
return resp, nil
}
default:
return nil, domain.ErrInvalidParams
}
}
func (s *CertificateService) ValidateCertificate(ctx context.Context, req *dtos.CertificateValidateCertificateReq) (*dtos.CertificateValidateCertificateResp, error) {
certX509, err := certs.ParseCertificateFromPEM(req.Certificate)
if err != nil {
return nil, err
} else if time.Now().After(certX509.NotAfter) {
return nil, fmt.Errorf("certificate has expired at %s", certX509.NotAfter.UTC().Format(time.RFC3339))
}
return &dtos.CertificateValidateCertificateResp{
IsValid: true,
Domains: strings.Join(certX509.DNSNames, ";"),
}, nil
}
func (s *CertificateService) ValidatePrivateKey(ctx context.Context, req *dtos.CertificateValidatePrivateKeyReq) (*dtos.CertificateValidatePrivateKeyResp, error) {
_, err := certcrypto.ParsePEMPrivateKey([]byte(req.PrivateKey))
if err != nil {
return nil, err
}
return &dtos.CertificateValidatePrivateKeyResp{
IsValid: true,
}, nil
}
func buildExpireSoonNotification(certificates []*domain.Certificate) *struct {
Subject string
Message string
} {
if len(certificates) == 0 {
return nil
}
subject := defaultExpireSubject
message := defaultExpireMessage
// 查询模板信息
settingsRepo := repository.NewSettingsRepository()
settings, err := settingsRepo.GetByName(context.Background(), "notifyTemplates")
if err == nil {
var templates *domain.NotifyTemplatesSettingsContent
json.Unmarshal([]byte(settings.Content), &templates)
if templates != nil && len(templates.NotifyTemplates) > 0 {
subject = templates.NotifyTemplates[0].Subject
message = templates.NotifyTemplates[0].Message
}
}
// 替换变量
count := len(certificates)
domains := make([]string, count)
for i, record := range certificates {
domains[i] = record.SubjectAltNames
}
countStr := strconv.Itoa(count)
domainStr := strings.Join(domains, ";")
subject = strings.ReplaceAll(subject, "${COUNT}", countStr)
subject = strings.ReplaceAll(subject, "${DOMAINS}", domainStr)
message = strings.ReplaceAll(message, "${COUNT}", countStr)
message = strings.ReplaceAll(message, "${DOMAINS}", domainStr)
// 返回消息
return &struct {
Subject string
Message string
}{Subject: subject, Message: message}
}

View File

@@ -1,84 +0,0 @@
package deployer
import (
"context"
"encoding/json"
"fmt"
cdn20180510 "github.com/alibabacloud-go/cdn-20180510/v5/client"
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
util "github.com/alibabacloud-go/tea-utils/v2/service"
"github.com/alibabacloud-go/tea/tea"
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/utils/rand"
)
type AliyunCDNDeployer struct {
client *cdn20180510.Client
option *DeployerOption
infos []string
}
func NewAliyunCDNDeployer(option *DeployerOption) (*AliyunCDNDeployer, error) {
access := &domain.AliyunAccess{}
json.Unmarshal([]byte(option.Access), access)
d := &AliyunCDNDeployer{
option: option,
}
client, err := d.createClient(access.AccessKeyId, access.AccessKeySecret)
if err != nil {
return nil, err
}
return &AliyunCDNDeployer{
client: client,
option: option,
infos: make([]string, 0),
}, nil
}
func (d *AliyunCDNDeployer) GetID() string {
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
}
func (d *AliyunCDNDeployer) GetInfo() []string {
return d.infos
}
func (d *AliyunCDNDeployer) Deploy(ctx context.Context) error {
certName := fmt.Sprintf("%s-%s-%s", d.option.Domain, d.option.DomainId, rand.RandStr(6))
setCdnDomainSSLCertificateRequest := &cdn20180510.SetCdnDomainSSLCertificateRequest{
DomainName: tea.String(getDeployString(d.option.DeployConfig, "domain")),
CertName: tea.String(certName),
CertType: tea.String("upload"),
SSLProtocol: tea.String("on"),
SSLPub: tea.String(d.option.Certificate.Certificate),
SSLPri: tea.String(d.option.Certificate.PrivateKey),
CertRegion: tea.String("cn-hangzhou"),
}
runtime := &util.RuntimeOptions{}
resp, err := d.client.SetCdnDomainSSLCertificateWithOptions(setCdnDomainSSLCertificateRequest, runtime)
if err != nil {
return err
}
d.infos = append(d.infos, toStr("cdn设置证书", resp))
return nil
}
func (d *AliyunCDNDeployer) createClient(accessKeyId, accessKeySecret string) (_result *cdn20180510.Client, _err error) {
config := &openapi.Config{
AccessKeyId: tea.String(accessKeyId),
AccessKeySecret: tea.String(accessKeySecret),
}
config.Endpoint = tea.String("cdn.aliyuncs.com")
_result = &cdn20180510.Client{}
_result, _err = cdn20180510.NewClient(config)
return _result, _err
}

View File

@@ -1,97 +0,0 @@
/*
* @Author: Bin
* @Date: 2024-09-17
* @FilePath: /certimate/internal/deployer/aliyun_esa.go
*/
package deployer
import (
"context"
"encoding/json"
"fmt"
"strings"
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
dcdn20180115 "github.com/alibabacloud-go/dcdn-20180115/v3/client"
util "github.com/alibabacloud-go/tea-utils/v2/service"
"github.com/alibabacloud-go/tea/tea"
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/utils/rand"
)
type AliyunESADeployer struct {
client *dcdn20180115.Client
option *DeployerOption
infos []string
}
func NewAliyunESADeployer(option *DeployerOption) (*AliyunESADeployer, error) {
access := &domain.AliyunAccess{}
json.Unmarshal([]byte(option.Access), access)
d := &AliyunESADeployer{
option: option,
}
client, err := d.createClient(access.AccessKeyId, access.AccessKeySecret)
if err != nil {
return nil, err
}
return &AliyunESADeployer{
client: client,
option: option,
infos: make([]string, 0),
}, nil
}
func (d *AliyunESADeployer) GetID() string {
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
}
func (d *AliyunESADeployer) GetInfo() []string {
return d.infos
}
func (d *AliyunESADeployer) Deploy(ctx context.Context) error {
certName := fmt.Sprintf("%s-%s-%s", d.option.Domain, d.option.DomainId, rand.RandStr(6))
// 支持泛解析域名,在 Aliyun DCND 中泛解析域名表示为 .example.com
domain := getDeployString(d.option.DeployConfig, "domain")
if strings.HasPrefix(domain, "*") {
domain = strings.TrimPrefix(domain, "*")
}
setDcdnDomainSSLCertificateRequest := &dcdn20180115.SetDcdnDomainSSLCertificateRequest{
DomainName: tea.String(domain),
CertName: tea.String(certName),
CertType: tea.String("upload"),
SSLProtocol: tea.String("on"),
SSLPub: tea.String(d.option.Certificate.Certificate),
SSLPri: tea.String(d.option.Certificate.PrivateKey),
CertRegion: tea.String("cn-hangzhou"),
}
runtime := &util.RuntimeOptions{}
resp, err := d.client.SetDcdnDomainSSLCertificateWithOptions(setDcdnDomainSSLCertificateRequest, runtime)
if err != nil {
return err
}
d.infos = append(d.infos, toStr("dcdn设置证书", resp))
return nil
}
func (d *AliyunESADeployer) createClient(accessKeyId, accessKeySecret string) (_result *dcdn20180115.Client, _err error) {
config := &openapi.Config{
AccessKeyId: tea.String(accessKeyId),
AccessKeySecret: tea.String(accessKeySecret),
}
config.Endpoint = tea.String("dcdn.aliyuncs.com")
_result = &dcdn20180115.Client{}
_result, _err = dcdn20180115.NewClient(config)
return _result, _err
}

View File

@@ -1,70 +0,0 @@
package deployer
import (
"context"
"encoding/json"
"fmt"
"github.com/aliyun/aliyun-oss-go-sdk/oss"
"github.com/usual2970/certimate/internal/domain"
)
type AliyunOSSDeployer struct {
client *oss.Client
option *DeployerOption
infos []string
}
func NewAliyunOSSDeployer(option *DeployerOption) (Deployer, error) {
access := &domain.AliyunAccess{}
json.Unmarshal([]byte(option.Access), access)
d := &AliyunOSSDeployer{
option: option,
infos: make([]string, 0),
}
client, err := d.createClient(access.AccessKeyId, access.AccessKeySecret)
if err != nil {
return nil, err
}
d.client = client
return d, nil
}
func (d *AliyunOSSDeployer) GetID() string {
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
}
func (d *AliyunOSSDeployer) GetInfo() []string {
return d.infos
}
func (d *AliyunOSSDeployer) Deploy(ctx context.Context) error {
err := d.client.PutBucketCnameWithCertificate(getDeployString(d.option.DeployConfig, "bucket"), oss.PutBucketCname{
Cname: getDeployString(d.option.DeployConfig, "domain"),
CertificateConfiguration: &oss.CertificateConfiguration{
Certificate: d.option.Certificate.Certificate,
PrivateKey: d.option.Certificate.PrivateKey,
Force: true,
},
})
if err != nil {
return fmt.Errorf("deploy aliyun oss error: %w", err)
}
return nil
}
func (d *AliyunOSSDeployer) createClient(accessKeyId, accessKeySecret string) (*oss.Client, error) {
client, err := oss.New(
getDeployString(d.option.DeployConfig, "endpoint"),
accessKeyId,
accessKeySecret,
)
if err != nil {
return nil, fmt.Errorf("create aliyun client error: %w", err)
}
return client, nil
}

View File

@@ -2,175 +2,72 @@ package deployer
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"github.com/pocketbase/pocketbase/models"
"github.com/usual2970/certimate/internal/applicant"
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/utils/app"
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/core/logger"
"github.com/usual2970/certimate/internal/repository"
)
const (
targetAliyunOSS = "aliyun-oss"
targetAliyunCDN = "aliyun-cdn"
targetAliyunESA = "aliyun-dcdn"
targetTencentCDN = "tencent-cdn"
targetTencentCOS = "tencent-cos"
targetHuaweiCloudCDN = "huaweicloud-cdn"
targetQiniuCdn = "qiniu-cdn"
targetLocal = "local"
targetSSH = "ssh"
targetWebhook = "webhook"
targetK8sSecret = "k8s-secret"
)
type DeployerOption struct {
DomainId string `json:"domainId"`
Domain string `json:"domain"`
Product string `json:"product"`
Access string `json:"access"`
AccessRecord *models.Record `json:"-"`
DeployConfig domain.DeployConfig `json:"deployConfig"`
Certificate applicant.Certificate `json:"certificate"`
Variables map[string]string `json:"variables"`
}
type Deployer interface {
Deploy(ctx context.Context) error
GetInfo() []string
GetID() string
}
func Gets(record *models.Record, cert *applicant.Certificate) ([]Deployer, error) {
rs := make([]Deployer, 0)
if record.GetString("deployConfig") == "" {
return rs, nil
type deployerOptions struct {
Provider domain.DeployProviderType
ProviderAccessConfig map[string]any
ProviderDeployConfig map[string]any
}
func NewWithDeployNode(node *domain.WorkflowNode, certdata struct {
Certificate string
PrivateKey string
},
) (Deployer, error) {
if node.Type != domain.WorkflowNodeTypeDeploy {
return nil, fmt.Errorf("node type is not deploy")
}
deployConfigs := make([]domain.DeployConfig, 0)
nodeConfig := node.GetConfigForDeploy()
err := record.UnmarshalJSONField("deployConfig", &deployConfigs)
accessRepo := repository.NewAccessRepository()
access, err := accessRepo.GetById(context.Background(), nodeConfig.ProviderAccessId)
if err != nil {
return nil, fmt.Errorf("解析部署配置失败: %w", err)
return nil, fmt.Errorf("failed to get access #%s record: %w", nodeConfig.ProviderAccessId, err)
}
if len(deployConfigs) == 0 {
return rs, nil
}
for _, deployConfig := range deployConfigs {
deployer, err := getWithDeployConfig(record, cert, deployConfig)
if err != nil {
return nil, err
}
rs = append(rs, deployer)
}
return rs, nil
}
func getWithDeployConfig(record *models.Record, cert *applicant.Certificate, deployConfig domain.DeployConfig) (Deployer, error) {
access, err := app.GetApp().Dao().FindRecordById("access", deployConfig.Access)
accessConfig, err := access.UnmarshalConfigToMap()
if err != nil {
return nil, fmt.Errorf("access record not found: %w", err)
return nil, fmt.Errorf("failed to unmarshal access config: %w", err)
}
option := &DeployerOption{
DomainId: record.Id,
Domain: record.GetString("domain"),
Product: getProduct(deployConfig.Type),
Access: access.GetString("config"),
AccessRecord: access,
DeployConfig: deployConfig,
}
if cert != nil {
option.Certificate = *cert
} else {
option.Certificate = applicant.Certificate{
Certificate: record.GetString("certificate"),
PrivateKey: record.GetString("privateKey"),
}
deployer, err := createDeployer(&deployerOptions{
Provider: domain.DeployProviderType(nodeConfig.Provider),
ProviderAccessConfig: accessConfig,
ProviderDeployConfig: nodeConfig.ProviderConfig,
})
if err != nil {
return nil, err
}
switch deployConfig.Type {
case targetAliyunOSS:
return NewAliyunOSSDeployer(option)
case targetAliyunCDN:
return NewAliyunCDNDeployer(option)
case targetAliyunESA:
return NewAliyunESADeployer(option)
case targetTencentCDN:
return NewTencentCDNDeployer(option)
case targetTencentCOS:
return NewTencentCOSDeployer(option)
case targetHuaweiCloudCDN:
return NewHuaweiCloudCDNDeployer(option)
case targetQiniuCdn:
return NewQiniuCDNDeployer(option)
case targetLocal:
return NewLocalDeployer(option)
case targetSSH:
return NewSSHDeployer(option)
case targetWebhook:
return NewWebhookDeployer(option)
case targetK8sSecret:
return NewK8sSecretDeployer(option)
}
return nil, errors.New("not implemented")
return &proxyDeployer{
logger: logger.NewNilLogger(),
deployer: deployer,
deployCertificate: certdata.Certificate,
deployPrivateKey: certdata.PrivateKey,
}, nil
}
func getProduct(t string) string {
rs := strings.Split(t, "-")
if len(rs) < 2 {
return ""
}
return rs[1]
// TODO: 暂时使用代理模式以兼容之前版本代码,后续重新实现此处逻辑
type proxyDeployer struct {
logger logger.Logger
deployer deployer.Deployer
deployCertificate string
deployPrivateKey string
}
func toStr(tag string, data any) string {
if data == nil {
return tag
}
byts, _ := json.Marshal(data)
return tag + "" + string(byts)
}
func getDeployString(conf domain.DeployConfig, key string) string {
if _, ok := conf.Config[key]; !ok {
return ""
}
val, ok := conf.Config[key].(string)
if !ok {
return ""
}
return val
}
func getDeployVariables(conf domain.DeployConfig) map[string]string {
rs := make(map[string]string)
data, ok := conf.Config["variables"]
if !ok {
return rs
}
bts, _ := json.Marshal(data)
kvData := make([]domain.KV, 0)
if err := json.Unmarshal(bts, &kvData); err != nil {
return rs
}
for _, kv := range kvData {
rs[kv.Key] = kv.Value
}
return rs
func (d *proxyDeployer) Deploy(ctx context.Context) error {
_, err := d.deployer.Deploy(ctx, d.deployCertificate, d.deployPrivateKey)
return err
}

View File

@@ -1,206 +0,0 @@
package deployer
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/global"
cdn "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2"
cdnModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2/model"
cdnRegion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2/region"
"github.com/usual2970/certimate/internal/domain"
uploader "github.com/usual2970/certimate/internal/pkg/core/uploader"
"github.com/usual2970/certimate/internal/pkg/utils/cast"
)
type HuaweiCloudCDNDeployer struct {
option *DeployerOption
infos []string
}
func NewHuaweiCloudCDNDeployer(option *DeployerOption) (Deployer, error) {
return &HuaweiCloudCDNDeployer{
option: option,
infos: make([]string, 0),
}, nil
}
func (d *HuaweiCloudCDNDeployer) GetID() string {
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
}
func (d *HuaweiCloudCDNDeployer) GetInfo() []string {
return d.infos
}
func (d *HuaweiCloudCDNDeployer) Deploy(ctx context.Context) error {
access := &domain.HuaweiCloudAccess{}
if err := json.Unmarshal([]byte(d.option.Access), access); err != nil {
return err
}
// TODO: CDN 服务与 DNS 服务所支持的区域可能不一致,这里暂时不传而是使用默认值,仅支持华为云国内版
client, err := d.createClient("", access.AccessKeyId, access.SecretAccessKey)
if err != nil {
return err
}
d.infos = append(d.infos, toStr("SDK 客户端创建成功", nil))
// 查询加速域名配置
// REF: https://support.huaweicloud.com/api-cdn/ShowDomainFullConfig.html
showDomainFullConfigReq := &cdnModel.ShowDomainFullConfigRequest{
DomainName: d.option.DeployConfig.GetConfigAsString("domain"),
}
showDomainFullConfigResp, err := client.ShowDomainFullConfig(showDomainFullConfigReq)
if err != nil {
return err
}
d.infos = append(d.infos, toStr("已查询到加速域名配置", showDomainFullConfigResp))
// 更新加速域名配置
// REF: https://support.huaweicloud.com/api-cdn/UpdateDomainMultiCertificates.html
// REF: https://support.huaweicloud.com/usermanual-cdn/cdn_01_0306.html
updateDomainMultiCertificatesReqBodyContent := &huaweicloudCDNUpdateDomainMultiCertificatesRequestBodyContent{}
updateDomainMultiCertificatesReqBodyContent.DomainName = d.option.DeployConfig.GetConfigAsString("domain")
updateDomainMultiCertificatesReqBodyContent.HttpsSwitch = 1
var updateDomainMultiCertificatesResp *cdnModel.UpdateDomainMultiCertificatesResponse
if d.option.DeployConfig.GetConfigAsBool("useSCM") {
uploader, err := uploader.NewHuaweiCloudSCMUploader(&uploader.HuaweiCloudSCMUploaderConfig{
Region: "", // TODO: SCM 服务与 DNS 服务所支持的区域可能不一致,这里暂时不传而是使用默认值,仅支持华为云国内版
AccessKeyId: access.AccessKeyId,
SecretAccessKey: access.SecretAccessKey,
})
if err != nil {
return err
}
// 上传证书到 SCM
uploadResult, err := uploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
if err != nil {
return err
}
d.infos = append(d.infos, toStr("已上传证书", uploadResult))
updateDomainMultiCertificatesReqBodyContent.CertificateType = cast.Int32Ptr(2)
updateDomainMultiCertificatesReqBodyContent.SCMCertificateId = cast.StringPtr(uploadResult.CertId)
updateDomainMultiCertificatesReqBodyContent.CertName = cast.StringPtr(uploadResult.CertName)
} else {
updateDomainMultiCertificatesReqBodyContent.CertificateType = cast.Int32Ptr(0)
updateDomainMultiCertificatesReqBodyContent.CertName = cast.StringPtr(fmt.Sprintf("certimate-%d", time.Now().UnixMilli()))
updateDomainMultiCertificatesReqBodyContent.Certificate = cast.StringPtr(d.option.Certificate.Certificate)
updateDomainMultiCertificatesReqBodyContent.PrivateKey = cast.StringPtr(d.option.Certificate.PrivateKey)
}
updateDomainMultiCertificatesReqBodyContent = mergeHuaweiCloudCDNConfig(showDomainFullConfigResp.Configs, updateDomainMultiCertificatesReqBodyContent)
updateDomainMultiCertificatesReq := &huaweicloudCDNUpdateDomainMultiCertificatesRequest{
Body: &huaweicloudCDNUpdateDomainMultiCertificatesRequestBody{
Https: updateDomainMultiCertificatesReqBodyContent,
},
}
updateDomainMultiCertificatesResp, err = executeHuaweiCloudCDNUploadDomainMultiCertificates(client, updateDomainMultiCertificatesReq)
if err != nil {
return err
}
d.infos = append(d.infos, toStr("已更新加速域名配置", updateDomainMultiCertificatesResp))
return nil
}
func (d *HuaweiCloudCDNDeployer) createClient(region, accessKeyId, secretAccessKey string) (*cdn.CdnClient, error) {
auth, err := global.NewCredentialsBuilder().
WithAk(accessKeyId).
WithSk(secretAccessKey).
SafeBuild()
if err != nil {
return nil, err
}
if region == "" {
region = "cn-north-1" // CDN 服务默认区域:华北一北京
}
hcRegion, err := cdnRegion.SafeValueOf(region)
if err != nil {
return nil, err
}
hcClient, err := cdn.CdnClientBuilder().
WithRegion(hcRegion).
WithCredential(auth).
SafeBuild()
if err != nil {
return nil, err
}
client := cdn.NewCdnClient(hcClient)
return client, nil
}
type huaweicloudCDNUpdateDomainMultiCertificatesRequestBodyContent struct {
cdnModel.UpdateDomainMultiCertificatesRequestBodyContent `json:",inline"`
SCMCertificateId *string `json:"scm_certificate_id,omitempty"`
}
type huaweicloudCDNUpdateDomainMultiCertificatesRequestBody struct {
Https *huaweicloudCDNUpdateDomainMultiCertificatesRequestBodyContent `json:"https,omitempty"`
}
type huaweicloudCDNUpdateDomainMultiCertificatesRequest struct {
Body *huaweicloudCDNUpdateDomainMultiCertificatesRequestBody `json:"body,omitempty"`
}
func executeHuaweiCloudCDNUploadDomainMultiCertificates(client *cdn.CdnClient, request *huaweicloudCDNUpdateDomainMultiCertificatesRequest) (*cdnModel.UpdateDomainMultiCertificatesResponse, error) {
// 华为云官方 SDK 中目前提供的字段缺失,这里暂时先需自定义请求
// 可能需要等之后 SDK 更新
requestDef := cdn.GenReqDefForUpdateDomainMultiCertificates()
if resp, err := client.HcClient.Sync(request, requestDef); err != nil {
return nil, err
} else {
return resp.(*cdnModel.UpdateDomainMultiCertificatesResponse), nil
}
}
func mergeHuaweiCloudCDNConfig(src *cdnModel.ConfigsGetBody, dest *huaweicloudCDNUpdateDomainMultiCertificatesRequestBodyContent) *huaweicloudCDNUpdateDomainMultiCertificatesRequestBodyContent {
if src == nil {
return dest
}
// 华为云 API 中不传的字段表示使用默认值、而非保留原值,因此这里需要把原配置中的参数重新赋值回去
// 而且蛋疼的是查询接口返回的数据结构和更新接口传入的参数结构不一致,需要做很多转化
if *src.OriginProtocol == "follow" {
dest.AccessOriginWay = cast.Int32Ptr(1)
} else if *src.OriginProtocol == "http" {
dest.AccessOriginWay = cast.Int32Ptr(2)
} else if *src.OriginProtocol == "https" {
dest.AccessOriginWay = cast.Int32Ptr(3)
}
if src.ForceRedirect != nil {
dest.ForceRedirectConfig = &cdnModel.ForceRedirect{}
if src.ForceRedirect.Status == "on" {
dest.ForceRedirectConfig.Switch = 1
dest.ForceRedirectConfig.RedirectType = src.ForceRedirect.Type
} else {
dest.ForceRedirectConfig.Switch = 0
}
}
if src.Https != nil {
if *src.Https.Http2Status == "on" {
dest.Http2 = cast.Int32Ptr(1)
}
}
return dest
}

View File

@@ -1,108 +0,0 @@
package deployer
import (
"context"
"encoding/json"
"fmt"
k8sMetaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"github.com/usual2970/certimate/internal/domain"
)
type K8sSecretDeployer struct {
option *DeployerOption
infos []string
}
func NewK8sSecretDeployer(option *DeployerOption) (Deployer, error) {
return &K8sSecretDeployer{
option: option,
infos: make([]string, 0),
}, nil
}
func (d *K8sSecretDeployer) GetID() string {
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
}
func (d *K8sSecretDeployer) GetInfo() []string {
return d.infos
}
func (d *K8sSecretDeployer) Deploy(ctx context.Context) error {
access := &domain.KubernetesAccess{}
if err := json.Unmarshal([]byte(d.option.Access), access); err != nil {
return err
}
client, err := d.createClient(access)
if err != nil {
return err
}
d.infos = append(d.infos, toStr("kubeClient 创建成功", nil))
namespace := getDeployString(d.option.DeployConfig, "namespace")
if namespace == "" {
namespace = "default"
}
secretName := getDeployString(d.option.DeployConfig, "secretName")
if secretName == "" {
return fmt.Errorf("k8s secret name is empty")
}
secretDataKeyForCrt := getDeployString(d.option.DeployConfig, "secretDataKeyForCrt")
if secretDataKeyForCrt == "" {
namespace = "tls.crt"
}
secretDataKeyForKey := getDeployString(d.option.DeployConfig, "secretDataKeyForKey")
if secretDataKeyForKey == "" {
namespace = "tls.key"
}
// 获取 Secret 实例
secret, err := client.CoreV1().Secrets(namespace).Get(context.TODO(), secretName, k8sMetaV1.GetOptions{})
if err != nil {
return fmt.Errorf("failed to get k8s secret: %w", err)
}
// 更新 Secret Data
secret.Data[secretDataKeyForCrt] = []byte(d.option.Certificate.Certificate)
secret.Data[secretDataKeyForKey] = []byte(d.option.Certificate.PrivateKey)
_, err = client.CoreV1().Secrets(namespace).Update(context.TODO(), secret, k8sMetaV1.UpdateOptions{})
if err != nil {
return fmt.Errorf("failed to update k8s secret: %w", err)
}
d.infos = append(d.infos, toStr("证书已更新到 K8s Secret", nil))
return nil
}
func (d *K8sSecretDeployer) createClient(access *domain.KubernetesAccess) (*kubernetes.Clientset, error) {
kubeConfig, err := clientcmd.Load([]byte(access.KubeConfig))
if err != nil {
return nil, err
}
clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
&clientcmd.ClientConfigLoadingRules{ExplicitPath: ""},
&clientcmd.ConfigOverrides{CurrentContext: kubeConfig.CurrentContext},
)
config, err := clientConfig.ClientConfig()
if err != nil {
return nil, err
}
client, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, err
}
return client, nil
}

View File

@@ -1,111 +0,0 @@
package deployer
import (
"context"
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"github.com/usual2970/certimate/internal/domain"
)
type LocalDeployer struct {
option *DeployerOption
infos []string
}
func NewLocalDeployer(option *DeployerOption) (Deployer, error) {
return &LocalDeployer{
option: option,
infos: make([]string, 0),
}, nil
}
func (d *LocalDeployer) GetID() string {
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
}
func (d *LocalDeployer) GetInfo() []string {
return []string{}
}
func (d *LocalDeployer) Deploy(ctx context.Context) error {
access := &domain.LocalAccess{}
if err := json.Unmarshal([]byte(d.option.Access), access); err != nil {
return err
}
preCommand := getDeployString(d.option.DeployConfig, "preCommand")
if preCommand != "" {
if err := execCmd(preCommand); err != nil {
return fmt.Errorf("执行前置命令失败: %w", err)
}
}
// 复制证书文件
if err := copyFile(getDeployString(d.option.DeployConfig, "certPath"), d.option.Certificate.Certificate); err != nil {
return fmt.Errorf("复制证书失败: %w", err)
}
// 复制私钥文件
if err := copyFile(getDeployString(d.option.DeployConfig, "keyPath"), d.option.Certificate.PrivateKey); err != nil {
return fmt.Errorf("复制私钥失败: %w", err)
}
// 执行命令
if err := execCmd(getDeployString(d.option.DeployConfig, "command")); err != nil {
return fmt.Errorf("执行命令失败: %w", err)
}
return nil
}
func execCmd(command string) error {
// 执行命令
var cmd *exec.Cmd
if runtime.GOOS == "windows" {
cmd = exec.Command("cmd", "/C", command)
} else {
cmd = exec.Command("sh", "-c", command)
}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
return fmt.Errorf("执行命令失败: %w", err)
}
return nil
}
func copyFile(path string, content string) error {
dir := filepath.Dir(path)
// 如果目录不存在,创建目录
err := os.MkdirAll(dir, os.ModePerm)
if err != nil {
return fmt.Errorf("创建目录失败: %w", err)
}
// 创建或打开文件
file, err := os.Create(path)
if err != nil {
return fmt.Errorf("创建文件失败: %w", err)
}
defer file.Close()
// 写入内容到文件
_, err = file.Write([]byte(content))
if err != nil {
return fmt.Errorf("写入文件失败: %w", err)
}
return nil
}

View File

@@ -0,0 +1,861 @@
package deployer
import (
"fmt"
"strings"
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/pkg/core/deployer"
p1PanelConsole "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/1panel-console"
p1PanelSite "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/1panel-site"
pAliyunALB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-alb"
pAliyunCASDeploy "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-cas-deploy"
pAliyunCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-cdn"
pAliyunCLB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-clb"
pAliyunDCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-dcdn"
pAliyunESA "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-esa"
pAliyunFC "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-fc"
pAliyunLive "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-live"
pAliyunNLB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-nlb"
pAliyunOSS "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-oss"
pAliyunVOD "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-vod"
pAliyunWAF "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-waf"
pAWSCloudFront "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aws-cloudfront"
pBaiduCloudCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baiducloud-cdn"
pBaishanCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baishan-cdn"
pBaotaPanelConsole "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baotapanel-console"
pBaotaPanelSite "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baotapanel-site"
pBytePlusCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/byteplus-cdn"
pCacheFly "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/cachefly"
pCdnfly "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/cdnfly"
pDogeCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/dogecloud-cdn"
pEdgioApplications "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/edgio-applications"
pGcoreCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/gcore-cdn"
pHuaweiCloudCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/huaweicloud-cdn"
pHuaweiCloudELB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/huaweicloud-elb"
pHuaweiCloudWAF "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/huaweicloud-waf"
pJDCloudALB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/jdcloud-alb"
pJDCloudCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/jdcloud-cdn"
pJDCloudLive "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/jdcloud-live"
pJDCloudVOD "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/jdcloud-vod"
pK8sSecret "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/k8s-secret"
pLocal "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/local"
pQiniuCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/qiniu-cdn"
pQiniuPili "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/qiniu-pili"
pSafeLine "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/safeline"
pSSH "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ssh"
pTencentCloudCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-cdn"
pTencentCloudCLB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-clb"
pTencentCloudCOS "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-cos"
pTencentCloudCSS "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-css"
pTencentCloudECDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-ecdn"
pTencentCloudEO "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-eo"
pTencentCloudSCF "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-scf"
pTencentCloudSSLDeploy "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-ssl-deploy"
pTencentCloudVOD "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-vod"
pTencentCloudWAF "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-waf"
pUCloudUCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ucloud-ucdn"
pUCloudUS3 "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ucloud-us3"
pVolcEngineCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-cdn"
pVolcEngineCLB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-clb"
pVolcEngineDCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-dcdn"
pVolcEngineImageX "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-imagex"
pVolcEngineLive "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-live"
pVolcEngineTOS "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-tos"
pWebhook "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/webhook"
"github.com/usual2970/certimate/internal/pkg/utils/maps"
"github.com/usual2970/certimate/internal/pkg/utils/slices"
)
func createDeployer(options *deployerOptions) (deployer.Deployer, error) {
/*
注意:如果追加新的常量值,请保持以 ASCII 排序。
NOTICE: If you add new constant, please keep ASCII order.
*/
switch options.Provider {
case domain.DeployProviderType1PanelConsole, domain.DeployProviderType1PanelSite:
{
access := domain.AccessConfigFor1Panel{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
switch options.Provider {
case domain.DeployProviderType1PanelConsole:
deployer, err := p1PanelConsole.NewDeployer(&p1PanelConsole.DeployerConfig{
ApiUrl: access.ApiUrl,
ApiKey: access.ApiKey,
AllowInsecureConnections: access.AllowInsecureConnections,
AutoRestart: maps.GetValueAsBool(options.ProviderDeployConfig, "autoRestart"),
})
return deployer, err
case domain.DeployProviderType1PanelSite:
deployer, err := p1PanelSite.NewDeployer(&p1PanelSite.DeployerConfig{
ApiUrl: access.ApiUrl,
ApiKey: access.ApiKey,
AllowInsecureConnections: access.AllowInsecureConnections,
WebsiteId: maps.GetValueAsInt64(options.ProviderDeployConfig, "websiteId"),
})
return deployer, err
default:
break
}
}
case domain.DeployProviderTypeAliyunALB, domain.DeployProviderTypeAliyunCASDeploy, domain.DeployProviderTypeAliyunCDN, domain.DeployProviderTypeAliyunCLB, domain.DeployProviderTypeAliyunDCDN, domain.DeployProviderTypeAliyunESA, domain.DeployProviderTypeAliyunFC, domain.DeployProviderTypeAliyunLive, domain.DeployProviderTypeAliyunNLB, domain.DeployProviderTypeAliyunOSS, domain.DeployProviderTypeAliyunVOD, domain.DeployProviderTypeAliyunWAF:
{
access := domain.AccessConfigForAliyun{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
switch options.Provider {
case domain.DeployProviderTypeAliyunALB:
deployer, err := pAliyunALB.NewDeployer(&pAliyunALB.DeployerConfig{
AccessKeyId: access.AccessKeyId,
AccessKeySecret: access.AccessKeySecret,
Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"),
ResourceType: pAliyunALB.ResourceType(maps.GetValueAsString(options.ProviderDeployConfig, "resourceType")),
LoadbalancerId: maps.GetValueAsString(options.ProviderDeployConfig, "loadbalancerId"),
ListenerId: maps.GetValueAsString(options.ProviderDeployConfig, "listenerId"),
Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
})
return deployer, err
case domain.DeployProviderTypeAliyunCASDeploy:
deployer, err := pAliyunCASDeploy.NewDeployer(&pAliyunCASDeploy.DeployerConfig{
AccessKeyId: access.AccessKeyId,
AccessKeySecret: access.AccessKeySecret,
Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"),
ResourceIds: slices.Filter(strings.Split(maps.GetValueAsString(options.ProviderDeployConfig, "resourceIds"), ";"), func(s string) bool { return s != "" }),
ContactIds: slices.Filter(strings.Split(maps.GetValueAsString(options.ProviderDeployConfig, "contactIds"), ";"), func(s string) bool { return s != "" }),
})
return deployer, err
case domain.DeployProviderTypeAliyunCDN:
deployer, err := pAliyunCDN.NewDeployer(&pAliyunCDN.DeployerConfig{
AccessKeyId: access.AccessKeyId,
AccessKeySecret: access.AccessKeySecret,
Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
})
return deployer, err
case domain.DeployProviderTypeAliyunCLB:
deployer, err := pAliyunCLB.NewDeployer(&pAliyunCLB.DeployerConfig{
AccessKeyId: access.AccessKeyId,
AccessKeySecret: access.AccessKeySecret,
Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"),
ResourceType: pAliyunCLB.ResourceType(maps.GetValueAsString(options.ProviderDeployConfig, "resourceType")),
LoadbalancerId: maps.GetValueAsString(options.ProviderDeployConfig, "loadbalancerId"),
ListenerPort: maps.GetValueOrDefaultAsInt32(options.ProviderDeployConfig, "listenerPort", 443),
Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
})
return deployer, err
case domain.DeployProviderTypeAliyunDCDN:
deployer, err := pAliyunDCDN.NewDeployer(&pAliyunDCDN.DeployerConfig{
AccessKeyId: access.AccessKeyId,
AccessKeySecret: access.AccessKeySecret,
Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
})
return deployer, err
case domain.DeployProviderTypeAliyunESA:
deployer, err := pAliyunESA.NewDeployer(&pAliyunESA.DeployerConfig{
AccessKeyId: access.AccessKeyId,
AccessKeySecret: access.AccessKeySecret,
Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"),
SiteId: maps.GetValueAsInt64(options.ProviderDeployConfig, "siteId"),
})
return deployer, err
case domain.DeployProviderTypeAliyunFC:
deployer, err := pAliyunFC.NewDeployer(&pAliyunFC.DeployerConfig{
AccessKeyId: access.AccessKeyId,
AccessKeySecret: access.AccessKeySecret,
Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"),
ServiceVersion: maps.GetValueAsString(options.ProviderDeployConfig, "serviceVersion"),
Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
})
return deployer, err
case domain.DeployProviderTypeAliyunLive:
deployer, err := pAliyunLive.NewDeployer(&pAliyunLive.DeployerConfig{
AccessKeyId: access.AccessKeyId,
AccessKeySecret: access.AccessKeySecret,
Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"),
Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
})
return deployer, err
case domain.DeployProviderTypeAliyunNLB:
deployer, err := pAliyunNLB.NewDeployer(&pAliyunNLB.DeployerConfig{
AccessKeyId: access.AccessKeyId,
AccessKeySecret: access.AccessKeySecret,
Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"),
ResourceType: pAliyunNLB.ResourceType(maps.GetValueAsString(options.ProviderDeployConfig, "resourceType")),
LoadbalancerId: maps.GetValueAsString(options.ProviderDeployConfig, "loadbalancerId"),
ListenerId: maps.GetValueAsString(options.ProviderDeployConfig, "listenerId"),
})
return deployer, err
case domain.DeployProviderTypeAliyunOSS:
deployer, err := pAliyunOSS.NewDeployer(&pAliyunOSS.DeployerConfig{
AccessKeyId: access.AccessKeyId,
AccessKeySecret: access.AccessKeySecret,
Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"),
Bucket: maps.GetValueAsString(options.ProviderDeployConfig, "bucket"),
Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
})
return deployer, err
case domain.DeployProviderTypeAliyunVOD:
deployer, err := pAliyunVOD.NewDeployer(&pAliyunVOD.DeployerConfig{
AccessKeyId: access.AccessKeyId,
AccessKeySecret: access.AccessKeySecret,
Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"),
Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
})
return deployer, err
case domain.DeployProviderTypeAliyunWAF:
deployer, err := pAliyunWAF.NewDeployer(&pAliyunWAF.DeployerConfig{
AccessKeyId: access.AccessKeyId,
AccessKeySecret: access.AccessKeySecret,
Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"),
InstanceId: maps.GetValueAsString(options.ProviderDeployConfig, "instanceId"),
})
return deployer, err
default:
break
}
}
case domain.DeployProviderTypeAWSCloudFront:
{
access := domain.AccessConfigForAWS{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
switch options.Provider {
case domain.DeployProviderTypeAWSCloudFront:
deployer, err := pAWSCloudFront.NewDeployer(&pAWSCloudFront.DeployerConfig{
AccessKeyId: access.AccessKeyId,
SecretAccessKey: access.SecretAccessKey,
Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"),
DistributionId: maps.GetValueAsString(options.ProviderDeployConfig, "distributionId"),
})
return deployer, err
default:
break
}
}
case domain.DeployProviderTypeBaiduCloudCDN:
{
access := domain.AccessConfigForBaiduCloud{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
switch options.Provider {
case domain.DeployProviderTypeBaiduCloudCDN:
deployer, err := pBaiduCloudCDN.NewDeployer(&pBaiduCloudCDN.DeployerConfig{
AccessKeyId: access.AccessKeyId,
SecretAccessKey: access.SecretAccessKey,
Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
})
return deployer, err
default:
break
}
}
case domain.DeployProviderTypeBaishanCDN:
{
access := domain.AccessConfigForBaishan{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
switch options.Provider {
case domain.DeployProviderTypeBaishanCDN:
deployer, err := pBaishanCDN.NewDeployer(&pBaishanCDN.DeployerConfig{
ApiToken: access.ApiToken,
Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
})
return deployer, err
default:
break
}
}
case domain.DeployProviderTypeBaotaPanelConsole, domain.DeployProviderTypeBaotaPanelSite:
{
access := domain.AccessConfigForBaotaPanel{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
switch options.Provider {
case domain.DeployProviderTypeBaotaPanelConsole:
deployer, err := pBaotaPanelConsole.NewDeployer(&pBaotaPanelConsole.DeployerConfig{
ApiUrl: access.ApiUrl,
ApiKey: access.ApiKey,
AllowInsecureConnections: access.AllowInsecureConnections,
AutoRestart: maps.GetValueAsBool(options.ProviderDeployConfig, "autoRestart"),
})
return deployer, err
case domain.DeployProviderTypeBaotaPanelSite:
deployer, err := pBaotaPanelSite.NewDeployer(&pBaotaPanelSite.DeployerConfig{
ApiUrl: access.ApiUrl,
ApiKey: access.ApiKey,
AllowInsecureConnections: access.AllowInsecureConnections,
SiteType: maps.GetValueOrDefaultAsString(options.ProviderDeployConfig, "siteType", "other"),
SiteName: maps.GetValueAsString(options.ProviderDeployConfig, "siteName"),
SiteNames: slices.Filter(strings.Split(maps.GetValueAsString(options.ProviderDeployConfig, "siteNames"), ";"), func(s string) bool { return s != "" }),
})
return deployer, err
default:
break
}
}
case domain.DeployProviderTypeBytePlusCDN:
{
access := domain.AccessConfigForBytePlus{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
switch options.Provider {
case domain.DeployProviderTypeBytePlusCDN:
deployer, err := pBytePlusCDN.NewDeployer(&pBytePlusCDN.DeployerConfig{
AccessKey: access.AccessKey,
SecretKey: access.SecretKey,
Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
})
return deployer, err
default:
break
}
}
case domain.DeployProviderTypeCacheFly:
{
access := domain.AccessConfigForCacheFly{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
deployer, err := pCacheFly.NewDeployer(&pCacheFly.DeployerConfig{
ApiToken: access.ApiToken,
})
return deployer, err
}
case domain.DeployProviderTypeCdnfly:
{
access := domain.AccessConfigForCdnfly{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
deployer, err := pCdnfly.NewDeployer(&pCdnfly.DeployerConfig{
ApiUrl: access.ApiUrl,
ApiKey: access.ApiKey,
ApiSecret: access.ApiSecret,
ResourceType: pCdnfly.ResourceType(maps.GetValueAsString(options.ProviderDeployConfig, "resourceType")),
SiteId: maps.GetValueAsString(options.ProviderDeployConfig, "siteId"),
CertificateId: maps.GetValueAsString(options.ProviderDeployConfig, "certificateId"),
})
return deployer, err
}
case domain.DeployProviderTypeDogeCloudCDN:
{
access := domain.AccessConfigForDogeCloud{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
deployer, err := pDogeCDN.NewDeployer(&pDogeCDN.DeployerConfig{
AccessKey: access.AccessKey,
SecretKey: access.SecretKey,
Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
})
return deployer, err
}
case domain.DeployProviderTypeEdgioApplications:
{
access := domain.AccessConfigForEdgio{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
deployer, err := pEdgioApplications.NewDeployer(&pEdgioApplications.DeployerConfig{
ClientId: access.ClientId,
ClientSecret: access.ClientSecret,
EnvironmentId: maps.GetValueAsString(options.ProviderDeployConfig, "environmentId"),
})
return deployer, err
}
case domain.DeployProviderTypeGcoreCDN:
{
access := domain.AccessConfigForGcore{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
switch options.Provider {
case domain.DeployProviderTypeGcoreCDN:
deployer, err := pGcoreCDN.NewDeployer(&pGcoreCDN.DeployerConfig{
ApiToken: access.ApiToken,
ResourceId: maps.GetValueAsInt64(options.ProviderDeployConfig, "resourceId"),
})
return deployer, err
default:
break
}
}
case domain.DeployProviderTypeHuaweiCloudCDN, domain.DeployProviderTypeHuaweiCloudELB, domain.DeployProviderTypeHuaweiCloudWAF:
{
access := domain.AccessConfigForHuaweiCloud{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
switch options.Provider {
case domain.DeployProviderTypeHuaweiCloudCDN:
deployer, err := pHuaweiCloudCDN.NewDeployer(&pHuaweiCloudCDN.DeployerConfig{
AccessKeyId: access.AccessKeyId,
SecretAccessKey: access.SecretAccessKey,
Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"),
Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
})
return deployer, err
case domain.DeployProviderTypeHuaweiCloudELB:
deployer, err := pHuaweiCloudELB.NewDeployer(&pHuaweiCloudELB.DeployerConfig{
AccessKeyId: access.AccessKeyId,
SecretAccessKey: access.SecretAccessKey,
Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"),
ResourceType: pHuaweiCloudELB.ResourceType(maps.GetValueAsString(options.ProviderDeployConfig, "resourceType")),
CertificateId: maps.GetValueAsString(options.ProviderDeployConfig, "certificateId"),
LoadbalancerId: maps.GetValueAsString(options.ProviderDeployConfig, "loadbalancerId"),
ListenerId: maps.GetValueAsString(options.ProviderDeployConfig, "listenerId"),
})
return deployer, err
case domain.DeployProviderTypeHuaweiCloudWAF:
deployer, err := pHuaweiCloudWAF.NewDeployer(&pHuaweiCloudWAF.DeployerConfig{
AccessKeyId: access.AccessKeyId,
SecretAccessKey: access.SecretAccessKey,
Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"),
ResourceType: pHuaweiCloudWAF.ResourceType(maps.GetValueAsString(options.ProviderDeployConfig, "resourceType")),
CertificateId: maps.GetValueAsString(options.ProviderDeployConfig, "certificateId"),
Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
})
return deployer, err
default:
break
}
}
case domain.DeployProviderTypeJDCloudALB, domain.DeployProviderTypeJDCloudCDN, domain.DeployProviderTypeJDCloudLive, domain.DeployProviderTypeJDCloudVOD:
{
access := domain.AccessConfigForJDCloud{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
switch options.Provider {
case domain.DeployProviderTypeJDCloudALB:
deployer, err := pJDCloudALB.NewDeployer(&pJDCloudALB.DeployerConfig{
AccessKeyId: access.AccessKeyId,
AccessKeySecret: access.AccessKeySecret,
RegionId: maps.GetValueAsString(options.ProviderDeployConfig, "regionId"),
ResourceType: pJDCloudALB.ResourceType(maps.GetValueAsString(options.ProviderDeployConfig, "resourceType")),
LoadbalancerId: maps.GetValueAsString(options.ProviderDeployConfig, "loadbalancerId"),
ListenerId: maps.GetValueAsString(options.ProviderDeployConfig, "listenerId"),
})
return deployer, err
case domain.DeployProviderTypeJDCloudCDN:
deployer, err := pJDCloudCDN.NewDeployer(&pJDCloudCDN.DeployerConfig{
AccessKeyId: access.AccessKeyId,
AccessKeySecret: access.AccessKeySecret,
Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
})
return deployer, err
case domain.DeployProviderTypeJDCloudLive:
deployer, err := pJDCloudLive.NewDeployer(&pJDCloudLive.DeployerConfig{
AccessKeyId: access.AccessKeyId,
AccessKeySecret: access.AccessKeySecret,
Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
})
return deployer, err
case domain.DeployProviderTypeJDCloudVOD:
deployer, err := pJDCloudVOD.NewDeployer(&pJDCloudVOD.DeployerConfig{
AccessKeyId: access.AccessKeyId,
AccessKeySecret: access.AccessKeySecret,
Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
})
return deployer, err
default:
break
}
}
case domain.DeployProviderTypeLocal:
{
deployer, err := pLocal.NewDeployer(&pLocal.DeployerConfig{
ShellEnv: pLocal.ShellEnvType(maps.GetValueAsString(options.ProviderDeployConfig, "shellEnv")),
PreCommand: maps.GetValueAsString(options.ProviderDeployConfig, "preCommand"),
PostCommand: maps.GetValueAsString(options.ProviderDeployConfig, "postCommand"),
OutputFormat: pLocal.OutputFormatType(maps.GetValueOrDefaultAsString(options.ProviderDeployConfig, "format", string(pLocal.OUTPUT_FORMAT_PEM))),
OutputCertPath: maps.GetValueAsString(options.ProviderDeployConfig, "certPath"),
OutputKeyPath: maps.GetValueAsString(options.ProviderDeployConfig, "keyPath"),
PfxPassword: maps.GetValueAsString(options.ProviderDeployConfig, "pfxPassword"),
JksAlias: maps.GetValueAsString(options.ProviderDeployConfig, "jksAlias"),
JksKeypass: maps.GetValueAsString(options.ProviderDeployConfig, "jksKeypass"),
JksStorepass: maps.GetValueAsString(options.ProviderDeployConfig, "jksStorepass"),
})
return deployer, err
}
case domain.DeployProviderTypeKubernetesSecret:
{
access := domain.AccessConfigForKubernetes{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
deployer, err := pK8sSecret.NewDeployer(&pK8sSecret.DeployerConfig{
KubeConfig: access.KubeConfig,
Namespace: maps.GetValueOrDefaultAsString(options.ProviderDeployConfig, "namespace", "default"),
SecretName: maps.GetValueAsString(options.ProviderDeployConfig, "secretName"),
SecretType: maps.GetValueOrDefaultAsString(options.ProviderDeployConfig, "secretType", "kubernetes.io/tls"),
SecretDataKeyForCrt: maps.GetValueOrDefaultAsString(options.ProviderDeployConfig, "secretDataKeyForCrt", "tls.crt"),
SecretDataKeyForKey: maps.GetValueOrDefaultAsString(options.ProviderDeployConfig, "secretDataKeyForKey", "tls.key"),
})
return deployer, err
}
case domain.DeployProviderTypeQiniuCDN, domain.DeployProviderTypeQiniuPili:
{
access := domain.AccessConfigForQiniu{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
switch options.Provider {
case domain.DeployProviderTypeQiniuCDN:
deployer, err := pQiniuCDN.NewDeployer(&pQiniuCDN.DeployerConfig{
AccessKey: access.AccessKey,
SecretKey: access.SecretKey,
Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
})
return deployer, err
case domain.DeployProviderTypeQiniuPili:
deployer, err := pQiniuPili.NewDeployer(&pQiniuPili.DeployerConfig{
AccessKey: access.AccessKey,
SecretKey: access.SecretKey,
Hub: maps.GetValueAsString(options.ProviderDeployConfig, "hub"),
Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
})
return deployer, err
default:
break
}
}
case domain.DeployProviderTypeSafeLine:
{
access := domain.AccessConfigForSafeLine{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
deployer, err := pSafeLine.NewDeployer(&pSafeLine.DeployerConfig{
ApiUrl: access.ApiUrl,
ApiToken: access.ApiToken,
AllowInsecureConnections: access.AllowInsecureConnections,
ResourceType: pSafeLine.ResourceType(maps.GetValueAsString(options.ProviderDeployConfig, "resourceType")),
CertificateId: maps.GetValueAsInt32(options.ProviderDeployConfig, "certificateId"),
})
return deployer, err
}
case domain.DeployProviderTypeSSH:
{
access := domain.AccessConfigForSSH{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
deployer, err := pSSH.NewDeployer(&pSSH.DeployerConfig{
SshHost: access.Host,
SshPort: access.Port,
SshUsername: access.Username,
SshPassword: access.Password,
SshKey: access.Key,
SshKeyPassphrase: access.KeyPassphrase,
UseSCP: maps.GetValueAsBool(options.ProviderDeployConfig, "useSCP"),
PreCommand: maps.GetValueAsString(options.ProviderDeployConfig, "preCommand"),
PostCommand: maps.GetValueAsString(options.ProviderDeployConfig, "postCommand"),
OutputFormat: pSSH.OutputFormatType(maps.GetValueOrDefaultAsString(options.ProviderDeployConfig, "format", string(pSSH.OUTPUT_FORMAT_PEM))),
OutputCertPath: maps.GetValueAsString(options.ProviderDeployConfig, "certPath"),
OutputKeyPath: maps.GetValueAsString(options.ProviderDeployConfig, "keyPath"),
PfxPassword: maps.GetValueAsString(options.ProviderDeployConfig, "pfxPassword"),
JksAlias: maps.GetValueAsString(options.ProviderDeployConfig, "jksAlias"),
JksKeypass: maps.GetValueAsString(options.ProviderDeployConfig, "jksKeypass"),
JksStorepass: maps.GetValueAsString(options.ProviderDeployConfig, "jksStorepass"),
})
return deployer, err
}
case domain.DeployProviderTypeTencentCloudCDN, domain.DeployProviderTypeTencentCloudCLB, domain.DeployProviderTypeTencentCloudCOS, domain.DeployProviderTypeTencentCloudCSS, domain.DeployProviderTypeTencentCloudECDN, domain.DeployProviderTypeTencentCloudEO, domain.DeployProviderTypeTencentCloudSCF, domain.DeployProviderTypeTencentCloudSSLDeploy, domain.DeployProviderTypeTencentCloudVOD, domain.DeployProviderTypeTencentCloudWAF:
{
access := domain.AccessConfigForTencentCloud{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
switch options.Provider {
case domain.DeployProviderTypeTencentCloudCDN:
deployer, err := pTencentCloudCDN.NewDeployer(&pTencentCloudCDN.DeployerConfig{
SecretId: access.SecretId,
SecretKey: access.SecretKey,
Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
})
return deployer, err
case domain.DeployProviderTypeTencentCloudCLB:
deployer, err := pTencentCloudCLB.NewDeployer(&pTencentCloudCLB.DeployerConfig{
SecretId: access.SecretId,
SecretKey: access.SecretKey,
Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"),
ResourceType: pTencentCloudCLB.ResourceType(maps.GetValueAsString(options.ProviderDeployConfig, "resourceType")),
LoadbalancerId: maps.GetValueAsString(options.ProviderDeployConfig, "loadbalancerId"),
ListenerId: maps.GetValueAsString(options.ProviderDeployConfig, "listenerId"),
Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
})
return deployer, err
case domain.DeployProviderTypeTencentCloudCOS:
deployer, err := pTencentCloudCOS.NewDeployer(&pTencentCloudCOS.DeployerConfig{
SecretId: access.SecretId,
SecretKey: access.SecretKey,
Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"),
Bucket: maps.GetValueAsString(options.ProviderDeployConfig, "bucket"),
Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
})
return deployer, err
case domain.DeployProviderTypeTencentCloudCSS:
deployer, err := pTencentCloudCSS.NewDeployer(&pTencentCloudCSS.DeployerConfig{
SecretId: access.SecretId,
SecretKey: access.SecretKey,
Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
})
return deployer, err
case domain.DeployProviderTypeTencentCloudECDN:
deployer, err := pTencentCloudECDN.NewDeployer(&pTencentCloudECDN.DeployerConfig{
SecretId: access.SecretId,
SecretKey: access.SecretKey,
Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
})
return deployer, err
case domain.DeployProviderTypeTencentCloudEO:
deployer, err := pTencentCloudEO.NewDeployer(&pTencentCloudEO.DeployerConfig{
SecretId: access.SecretId,
SecretKey: access.SecretKey,
ZoneId: maps.GetValueAsString(options.ProviderDeployConfig, "zoneId"),
Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
})
return deployer, err
case domain.DeployProviderTypeTencentCloudSCF:
deployer, err := pTencentCloudSCF.NewDeployer(&pTencentCloudSCF.DeployerConfig{
SecretId: access.SecretId,
SecretKey: access.SecretKey,
Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"),
Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
})
return deployer, err
case domain.DeployProviderTypeTencentCloudSSLDeploy:
deployer, err := pTencentCloudSSLDeploy.NewDeployer(&pTencentCloudSSLDeploy.DeployerConfig{
SecretId: access.SecretId,
SecretKey: access.SecretKey,
Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"),
ResourceType: maps.GetValueAsString(options.ProviderDeployConfig, "resourceType"),
ResourceIds: slices.Filter(strings.Split(maps.GetValueAsString(options.ProviderDeployConfig, "resourceIds"), ";"), func(s string) bool { return s != "" }),
})
return deployer, err
case domain.DeployProviderTypeTencentCloudVOD:
deployer, err := pTencentCloudVOD.NewDeployer(&pTencentCloudVOD.DeployerConfig{
SecretId: access.SecretId,
SecretKey: access.SecretKey,
SubAppId: maps.GetValueAsInt64(options.ProviderDeployConfig, "subAppId"),
Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
})
return deployer, err
case domain.DeployProviderTypeTencentCloudWAF:
deployer, err := pTencentCloudWAF.NewDeployer(&pTencentCloudWAF.DeployerConfig{
SecretId: access.SecretId,
SecretKey: access.SecretKey,
Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
DomainId: maps.GetValueAsString(options.ProviderDeployConfig, "domainId"),
InstanceId: maps.GetValueAsString(options.ProviderDeployConfig, "instanceId"),
})
return deployer, err
default:
break
}
}
case domain.DeployProviderTypeUCloudUCDN, domain.DeployProviderTypeUCloudUS3:
{
access := domain.AccessConfigForUCloud{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
switch options.Provider {
case domain.DeployProviderTypeUCloudUCDN:
deployer, err := pUCloudUCDN.NewDeployer(&pUCloudUCDN.DeployerConfig{
PrivateKey: access.PrivateKey,
PublicKey: access.PublicKey,
ProjectId: access.ProjectId,
DomainId: maps.GetValueAsString(options.ProviderDeployConfig, "domainId"),
})
return deployer, err
case domain.DeployProviderTypeUCloudUS3:
deployer, err := pUCloudUS3.NewDeployer(&pUCloudUS3.DeployerConfig{
PrivateKey: access.PrivateKey,
PublicKey: access.PublicKey,
ProjectId: access.ProjectId,
Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"),
Bucket: maps.GetValueAsString(options.ProviderDeployConfig, "bucket"),
Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
})
return deployer, err
default:
break
}
}
case domain.DeployProviderTypeVolcEngineCDN, domain.DeployProviderTypeVolcEngineCLB, domain.DeployProviderTypeVolcEngineDCDN, domain.DeployProviderTypeVolcEngineImageX, domain.DeployProviderTypeVolcEngineLive, domain.DeployProviderTypeVolcEngineTOS:
{
access := domain.AccessConfigForVolcEngine{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
switch options.Provider {
case domain.DeployProviderTypeVolcEngineCDN:
deployer, err := pVolcEngineCDN.NewDeployer(&pVolcEngineCDN.DeployerConfig{
AccessKeyId: access.AccessKeyId,
AccessKeySecret: access.SecretAccessKey,
Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
})
return deployer, err
case domain.DeployProviderTypeVolcEngineCLB:
deployer, err := pVolcEngineCLB.NewDeployer(&pVolcEngineCLB.DeployerConfig{
AccessKeyId: access.AccessKeyId,
AccessKeySecret: access.SecretAccessKey,
Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"),
ResourceType: pVolcEngineCLB.ResourceType(maps.GetValueAsString(options.ProviderDeployConfig, "resourceType")),
ListenerId: maps.GetValueAsString(options.ProviderDeployConfig, "listenerId"),
})
return deployer, err
case domain.DeployProviderTypeVolcEngineDCDN:
deployer, err := pVolcEngineDCDN.NewDeployer(&pVolcEngineDCDN.DeployerConfig{
AccessKeyId: access.AccessKeyId,
AccessKeySecret: access.SecretAccessKey,
Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
})
return deployer, err
case domain.DeployProviderTypeVolcEngineImageX:
deployer, err := pVolcEngineImageX.NewDeployer(&pVolcEngineImageX.DeployerConfig{
AccessKeyId: access.AccessKeyId,
AccessKeySecret: access.SecretAccessKey,
Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"),
ServiceId: maps.GetValueAsString(options.ProviderDeployConfig, "serviceId"),
Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
})
return deployer, err
case domain.DeployProviderTypeVolcEngineLive:
deployer, err := pVolcEngineLive.NewDeployer(&pVolcEngineLive.DeployerConfig{
AccessKeyId: access.AccessKeyId,
AccessKeySecret: access.SecretAccessKey,
Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
})
return deployer, err
case domain.DeployProviderTypeVolcEngineTOS:
deployer, err := pVolcEngineTOS.NewDeployer(&pVolcEngineTOS.DeployerConfig{
AccessKeyId: access.AccessKeyId,
AccessKeySecret: access.SecretAccessKey,
Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"),
Bucket: maps.GetValueAsString(options.ProviderDeployConfig, "bucket"),
Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"),
})
return deployer, err
default:
break
}
}
case domain.DeployProviderTypeWebhook:
{
access := domain.AccessConfigForWebhook{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
deployer, err := pWebhook.NewDeployer(&pWebhook.DeployerConfig{
WebhookUrl: access.Url,
WebhookData: maps.GetValueAsString(options.ProviderDeployConfig, "webhookData"),
AllowInsecureConnections: access.AllowInsecureConnections,
})
return deployer, err
}
}
return nil, fmt.Errorf("unsupported deployer provider: %s", string(options.Provider))
}

View File

@@ -1,214 +0,0 @@
package deployer
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"github.com/qiniu/go-sdk/v7/auth"
"github.com/usual2970/certimate/internal/domain"
xhttp "github.com/usual2970/certimate/internal/utils/http"
)
const qiniuGateway = "http://api.qiniu.com"
type QiniuCDNDeployer struct {
option *DeployerOption
info []string
credentials *auth.Credentials
}
func NewQiniuCDNDeployer(option *DeployerOption) (*QiniuCDNDeployer, error) {
access := &domain.QiniuAccess{}
json.Unmarshal([]byte(option.Access), access)
return &QiniuCDNDeployer{
option: option,
info: make([]string, 0),
credentials: auth.New(access.AccessKey, access.SecretKey),
}, nil
}
func (d *QiniuCDNDeployer) GetID() string {
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
}
func (d *QiniuCDNDeployer) GetInfo() []string {
return d.info
}
func (d *QiniuCDNDeployer) Deploy(ctx context.Context) error {
// 上传证书
certId, err := d.uploadCert()
if err != nil {
return fmt.Errorf("uploadCert failed: %w", err)
}
// 获取域名信息
domainInfo, err := d.getDomainInfo()
if err != nil {
return fmt.Errorf("getDomainInfo failed: %w", err)
}
// 判断域名是否启用 https
if domainInfo.Https != nil && domainInfo.Https.CertID != "" {
// 启用了 https
// 修改域名证书
err = d.modifyDomainCert(certId)
if err != nil {
return fmt.Errorf("modifyDomainCert failed: %w", err)
}
} else {
// 没启用 https
// 启用 https
err = d.enableHttps(certId)
if err != nil {
return fmt.Errorf("enableHttps failed: %w", err)
}
}
return nil
}
func (d *QiniuCDNDeployer) enableHttps(certId string) error {
domain := d.option.DeployConfig.GetDomain()
path := fmt.Sprintf("/domain/%s/sslize", domain)
body := &qiniuModifyDomainCertReq{
CertID: certId,
ForceHttps: true,
Http2Enable: true,
}
bodyBytes, err := json.Marshal(body)
if err != nil {
return fmt.Errorf("enable https failed: %w", err)
}
_, err = d.req(qiniuGateway+path, http.MethodPut, bytes.NewReader(bodyBytes))
if err != nil {
return fmt.Errorf("enable https failed: %w", err)
}
return nil
}
type qiniuDomainInfo struct {
Https *qiniuModifyDomainCertReq `json:"https"`
}
func (d *QiniuCDNDeployer) getDomainInfo() (*qiniuDomainInfo, error) {
domain := d.option.DeployConfig.GetDomain()
path := fmt.Sprintf("/domain/%s", domain)
res, err := d.req(qiniuGateway+path, http.MethodGet, nil)
if err != nil {
return nil, fmt.Errorf("req failed: %w", err)
}
resp := &qiniuDomainInfo{}
err = json.Unmarshal(res, resp)
if err != nil {
return nil, fmt.Errorf("json.Unmarshal failed: %w", err)
}
return resp, nil
}
type qiniuUploadCertReq struct {
Name string `json:"name"`
CommonName string `json:"common_name"`
Pri string `json:"pri"`
Ca string `json:"ca"`
}
type qiniuUploadCertResp struct {
CertID string `json:"certID"`
}
func (d *QiniuCDNDeployer) uploadCert() (string, error) {
path := "/sslcert"
body := &qiniuUploadCertReq{
Name: getDeployString(d.option.DeployConfig, "domain"),
CommonName: getDeployString(d.option.DeployConfig, "domain"),
Pri: d.option.Certificate.PrivateKey,
Ca: d.option.Certificate.Certificate,
}
bodyBytes, err := json.Marshal(body)
if err != nil {
return "", fmt.Errorf("json.Marshal failed: %w", err)
}
res, err := d.req(qiniuGateway+path, http.MethodPost, bytes.NewReader(bodyBytes))
if err != nil {
return "", fmt.Errorf("req failed: %w", err)
}
resp := &qiniuUploadCertResp{}
err = json.Unmarshal(res, resp)
if err != nil {
return "", fmt.Errorf("json.Unmarshal failed: %w", err)
}
return resp.CertID, nil
}
type qiniuModifyDomainCertReq struct {
CertID string `json:"certId"`
ForceHttps bool `json:"forceHttps"`
Http2Enable bool `json:"http2Enable"`
}
func (d *QiniuCDNDeployer) modifyDomainCert(certId string) error {
domain := d.option.DeployConfig.GetDomain()
path := fmt.Sprintf("/domain/%s/httpsconf", domain)
body := &qiniuModifyDomainCertReq{
CertID: certId,
ForceHttps: true,
Http2Enable: true,
}
bodyBytes, err := json.Marshal(body)
if err != nil {
return fmt.Errorf("json.Marshal failed: %w", err)
}
_, err = d.req(qiniuGateway+path, http.MethodPut, bytes.NewReader(bodyBytes))
if err != nil {
return fmt.Errorf("req failed: %w", err)
}
return nil
}
func (d *QiniuCDNDeployer) req(url, method string, body io.Reader) ([]byte, error) {
req := xhttp.BuildReq(url, method, body, map[string]string{
"Content-Type": "application/json",
})
if err := d.credentials.AddToken(auth.TokenQBox, req); err != nil {
return nil, fmt.Errorf("credentials.AddToken failed: %w", err)
}
respBody, err := xhttp.ToRequest(req)
if err != nil {
return nil, fmt.Errorf("ToRequest failed: %w", err)
}
defer respBody.Close()
res, err := io.ReadAll(respBody)
if err != nil {
return nil, fmt.Errorf("io.ReadAll failed: %w", err)
}
return res, nil
}

View File

@@ -1,87 +0,0 @@
package deployer
import (
"testing"
"github.com/qiniu/go-sdk/v7/auth"
"github.com/usual2970/certimate/internal/applicant"
)
func Test_qiuniu_uploadCert(t *testing.T) {
type fields struct {
option *DeployerOption
}
tests := []struct {
name string
fields fields
want string
wantErr bool
}{
{
name: "test",
fields: fields{
option: &DeployerOption{
DomainId: "1",
Domain: "example.com",
Product: "test",
Access: `{"bucket":"test","accessKey":"","secretKey":""}`,
Certificate: applicant.Certificate{
Certificate: "",
PrivateKey: "",
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
q, _ := NewQiniuCDNDeployer(tt.fields.option)
got, err := q.uploadCert()
if (err != nil) != tt.wantErr {
t.Errorf("qiuniu.uploadCert() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("qiuniu.uploadCert() = %v, want %v", got, tt.want)
}
})
}
}
func Test_qiuniu_modifyDomainCert(t *testing.T) {
type fields struct {
option *DeployerOption
info []string
credentials *auth.Credentials
}
type args struct {
certId string
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
{
name: "test",
fields: fields{
option: &DeployerOption{
DomainId: "1",
Domain: "jt1.ikit.fun",
Product: "test",
Access: `{"bucket":"test","accessKey":"","secretKey":""}`,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
q, _ := NewQiniuCDNDeployer(tt.fields.option)
if err := q.modifyDomainCert(tt.args.certId); (err != nil) != tt.wantErr {
t.Errorf("qiuniu.modifyDomainCert() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

View File

@@ -1,154 +0,0 @@
package deployer
import (
"bytes"
"context"
"encoding/json"
"fmt"
"os"
xpath "path"
"github.com/pkg/sftp"
sshPkg "golang.org/x/crypto/ssh"
"github.com/usual2970/certimate/internal/domain"
)
type SSHDeployer struct {
option *DeployerOption
infos []string
}
func NewSSHDeployer(option *DeployerOption) (Deployer, error) {
return &SSHDeployer{
option: option,
infos: make([]string, 0),
}, nil
}
func (d *SSHDeployer) GetID() string {
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
}
func (d *SSHDeployer) GetInfo() []string {
return d.infos
}
func (d *SSHDeployer) Deploy(ctx context.Context) error {
access := &domain.SSHAccess{}
if err := json.Unmarshal([]byte(d.option.Access), access); err != nil {
return err
}
// 连接
client, err := d.createClient(access)
if err != nil {
return err
}
defer client.Close()
d.infos = append(d.infos, toStr("ssh连接成功", nil))
// 执行前置命令
preCommand := getDeployString(d.option.DeployConfig, "preCommand")
if preCommand != "" {
stdout, stderr, err := d.sshExecCommand(client, preCommand)
if err != nil {
return fmt.Errorf("failed to run pre-command: %w, stdout: %s, stderr: %s", err, stdout, stderr)
}
}
// 上传证书
if err := d.upload(client, d.option.Certificate.Certificate, getDeployString(d.option.DeployConfig, "certPath")); err != nil {
return fmt.Errorf("failed to upload certificate: %w", err)
}
d.infos = append(d.infos, toStr("ssh上传证书成功", nil))
// 上传私钥
if err := d.upload(client, d.option.Certificate.PrivateKey, getDeployString(d.option.DeployConfig, "keyPath")); err != nil {
return fmt.Errorf("failed to upload private key: %w", err)
}
d.infos = append(d.infos, toStr("ssh上传私钥成功", nil))
// 执行命令
stdout, stderr, err := d.sshExecCommand(client, getDeployString(d.option.DeployConfig, "command"))
if err != nil {
return fmt.Errorf("failed to run command: %w, stdout: %s, stderr: %s", err, stdout, stderr)
}
d.infos = append(d.infos, toStr("ssh执行命令成功", stdout))
return nil
}
func (d *SSHDeployer) sshExecCommand(client *sshPkg.Client, command string) (string, string, error) {
session, err := client.NewSession()
if err != nil {
return "", "", fmt.Errorf("failed to create ssh session: %w", err)
}
defer session.Close()
var stdoutBuf bytes.Buffer
session.Stdout = &stdoutBuf
var stderrBuf bytes.Buffer
session.Stderr = &stderrBuf
err = session.Run(command)
return stdoutBuf.String(), stderrBuf.String(), err
}
func (d *SSHDeployer) upload(client *sshPkg.Client, content, path string) error {
sftpCli, err := sftp.NewClient(client)
if err != nil {
return fmt.Errorf("failed to create sftp client: %w", err)
}
defer sftpCli.Close()
if err := sftpCli.MkdirAll(xpath.Dir(path)); err != nil {
return fmt.Errorf("failed to create remote directory: %w", err)
}
file, err := sftpCli.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC)
if err != nil {
return fmt.Errorf("failed to open remote file: %w", err)
}
defer file.Close()
_, err = file.Write([]byte(content))
if err != nil {
return fmt.Errorf("failed to write to remote file: %w", err)
}
return nil
}
func (d *SSHDeployer) createClient(access *domain.SSHAccess) (*sshPkg.Client, error) {
var authMethod sshPkg.AuthMethod
if access.Key != "" {
var signer sshPkg.Signer
var err error
if access.KeyPassphrase != "" {
signer, err = sshPkg.ParsePrivateKeyWithPassphrase([]byte(access.Key), []byte(access.KeyPassphrase))
} else {
signer, err = sshPkg.ParsePrivateKey([]byte(access.Key))
}
if err != nil {
return nil, err
}
authMethod = sshPkg.PublicKeys(signer)
} else {
authMethod = sshPkg.Password(access.Password)
}
return sshPkg.Dial("tcp", fmt.Sprintf("%s:%s", access.Host, access.Port), &sshPkg.ClientConfig{
User: access.Username,
Auth: []sshPkg.AuthMethod{
authMethod,
},
HostKeyCallback: sshPkg.InsecureIgnoreHostKey(),
})
}

View File

@@ -1,12 +0,0 @@
package deployer
import (
"os"
"path"
"testing"
)
func TestPath(t *testing.T) {
dir := path.Dir("./a/b/c")
os.MkdirAll(dir, 0o755)
}

View File

@@ -1,108 +0,0 @@
package deployer
import (
"context"
"encoding/json"
"fmt"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
ssl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205"
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/utils/rand"
)
type TencentCOSDeployer struct {
option *DeployerOption
credential *common.Credential
infos []string
}
func NewTencentCOSDeployer(option *DeployerOption) (Deployer, error) {
access := &domain.TencentAccess{}
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
return nil, fmt.Errorf("failed to unmarshal tencent access: %w", err)
}
credential := common.NewCredential(
access.SecretId,
access.SecretKey,
)
return &TencentCOSDeployer{
option: option,
credential: credential,
infos: make([]string, 0),
}, nil
}
func (d *TencentCOSDeployer) GetID() string {
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
}
func (d *TencentCOSDeployer) GetInfo() []string {
return d.infos
}
func (d *TencentCOSDeployer) Deploy(ctx context.Context) error {
// 上传证书
certId, err := d.uploadCert()
if err != nil {
return fmt.Errorf("failed to upload certificate: %w", err)
}
d.infos = append(d.infos, toStr("上传证书", certId))
if err := d.deploy(certId); err != nil {
return fmt.Errorf("failed to deploy: %w", err)
}
return nil
}
// 上传证书与CDN部署的上传方法一致。
func (d *TencentCOSDeployer) uploadCert() (string, error) {
cpf := profile.NewClientProfile()
cpf.HttpProfile.Endpoint = "ssl.tencentcloudapi.com"
client, _ := ssl.NewClient(d.credential, "", cpf)
request := ssl.NewUploadCertificateRequest()
request.CertificatePublicKey = common.StringPtr(d.option.Certificate.Certificate)
request.CertificatePrivateKey = common.StringPtr(d.option.Certificate.PrivateKey)
request.Alias = common.StringPtr(d.option.Domain + "_" + rand.RandStr(6))
request.Repeatable = common.BoolPtr(false)
response, err := client.UploadCertificate(request)
if err != nil {
return "", fmt.Errorf("failed to upload certificate: %w", err)
}
return *response.Response.CertificateId, nil
}
func (d *TencentCOSDeployer) deploy(certId string) error {
cpf := profile.NewClientProfile()
cpf.HttpProfile.Endpoint = "ssl.tencentcloudapi.com"
// 实例化要请求产品的client对象,clientProfile是可选的
client, _ := ssl.NewClient(d.credential, getDeployString(d.option.DeployConfig, "region"), cpf)
// 实例化一个请求对象,每个接口都会对应一个request对象
request := ssl.NewDeployCertificateInstanceRequest()
request.CertificateId = common.StringPtr(certId)
request.ResourceType = common.StringPtr("cos")
request.Status = common.Int64Ptr(1)
domain := getDeployString(d.option.DeployConfig, "domain")
request.InstanceIdList = common.StringPtrs([]string{fmt.Sprintf("%s#%s#%s", getDeployString(d.option.DeployConfig, "region"), getDeployString(d.option.DeployConfig, "bucket"), domain)})
// 返回的resp是一个DeployCertificateInstanceResponse的实例与请求对象对应
resp, err := client.DeployCertificateInstance(request)
if err != nil {
return fmt.Errorf("failed to deploy certificate: %w", err)
}
d.infos = append(d.infos, toStr("部署证书", resp.Response))
return nil
}

View File

@@ -1,145 +0,0 @@
package deployer
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"strings"
cdn "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn/v20180606"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
ssl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205"
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/utils/rand"
)
type TencentCDNDeployer struct {
option *DeployerOption
credential *common.Credential
infos []string
}
func NewTencentCDNDeployer(option *DeployerOption) (Deployer, error) {
access := &domain.TencentAccess{}
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
return nil, fmt.Errorf("failed to unmarshal tencent access: %w", err)
}
credential := common.NewCredential(
access.SecretId,
access.SecretKey,
)
return &TencentCDNDeployer{
option: option,
credential: credential,
infos: make([]string, 0),
}, nil
}
func (d *TencentCDNDeployer) GetID() string {
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
}
func (d *TencentCDNDeployer) GetInfo() []string {
return d.infos
}
func (d *TencentCDNDeployer) Deploy(ctx context.Context) error {
// 上传证书
certId, err := d.uploadCert()
if err != nil {
return fmt.Errorf("failed to upload certificate: %w", err)
}
d.infos = append(d.infos, toStr("上传证书", certId))
if err := d.deploy(certId); err != nil {
return fmt.Errorf("failed to deploy: %w", err)
}
return nil
}
func (d *TencentCDNDeployer) uploadCert() (string, error) {
cpf := profile.NewClientProfile()
cpf.HttpProfile.Endpoint = "ssl.tencentcloudapi.com"
client, _ := ssl.NewClient(d.credential, "", cpf)
request := ssl.NewUploadCertificateRequest()
request.CertificatePublicKey = common.StringPtr(d.option.Certificate.Certificate)
request.CertificatePrivateKey = common.StringPtr(d.option.Certificate.PrivateKey)
request.Alias = common.StringPtr(d.option.Domain + "_" + rand.RandStr(6))
request.Repeatable = common.BoolPtr(false)
response, err := client.UploadCertificate(request)
if err != nil {
return "", fmt.Errorf("failed to upload certificate: %w", err)
}
return *response.Response.CertificateId, nil
}
func (d *TencentCDNDeployer) deploy(certId string) error {
cpf := profile.NewClientProfile()
cpf.HttpProfile.Endpoint = "ssl.tencentcloudapi.com"
// 实例化要请求产品的client对象,clientProfile是可选的
client, _ := ssl.NewClient(d.credential, "", cpf)
// 实例化一个请求对象,每个接口都会对应一个request对象
request := ssl.NewDeployCertificateInstanceRequest()
request.CertificateId = common.StringPtr(certId)
request.ResourceType = common.StringPtr("cdn")
request.Status = common.Int64Ptr(1)
// 如果是泛域名就从cdn列表下获取SSL证书中的可用域名
domain := getDeployString(d.option.DeployConfig, "domain")
if strings.Contains(domain, "*") {
list, errGetList := d.getDomainList()
if errGetList != nil {
return fmt.Errorf("failed to get certificate domain list: %w", errGetList)
}
if list == nil || len(list) == 0 {
return fmt.Errorf("failed to get certificate domain list: empty list.")
}
request.InstanceIdList = common.StringPtrs(list)
} else { // 否则直接使用传入的域名
request.InstanceIdList = common.StringPtrs([]string{domain})
}
// 返回的resp是一个DeployCertificateInstanceResponse的实例与请求对象对应
resp, err := client.DeployCertificateInstance(request)
if err != nil {
return fmt.Errorf("failed to deploy certificate: %w", err)
}
d.infos = append(d.infos, toStr("部署证书", resp.Response))
return nil
}
func (d *TencentCDNDeployer) getDomainList() ([]string, error) {
cpf := profile.NewClientProfile()
cpf.HttpProfile.Endpoint = "cdn.tencentcloudapi.com"
client, _ := cdn.NewClient(d.credential, "", cpf)
request := cdn.NewDescribeCertDomainsRequest()
cert := base64.StdEncoding.EncodeToString([]byte(d.option.Certificate.Certificate))
request.Cert = &cert
response, err := client.DescribeCertDomains(request)
if err != nil {
return nil, fmt.Errorf("failed to get domain list: %w", err)
}
domains := make([]string, 0)
for _, domain := range response.Response.Domains {
domains = append(domains, *domain)
}
return domains, nil
}

View File

@@ -1,66 +0,0 @@
package deployer
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"github.com/usual2970/certimate/internal/domain"
xhttp "github.com/usual2970/certimate/internal/utils/http"
)
type WebhookDeployer struct {
option *DeployerOption
infos []string
}
func NewWebhookDeployer(option *DeployerOption) (Deployer, error) {
return &WebhookDeployer{
option: option,
infos: make([]string, 0),
}, nil
}
func (d *WebhookDeployer) GetID() string {
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
}
func (d *WebhookDeployer) GetInfo() []string {
return d.infos
}
type webhookData struct {
Domain string `json:"domain"`
Certificate string `json:"certificate"`
PrivateKey string `json:"privateKey"`
Variables map[string]string `json:"variables"`
}
func (d *WebhookDeployer) Deploy(ctx context.Context) error {
access := &domain.WebhookAccess{}
if err := json.Unmarshal([]byte(d.option.Access), access); err != nil {
return fmt.Errorf("failed to parse hook access config: %w", err)
}
data := &webhookData{
Domain: d.option.Domain,
Certificate: d.option.Certificate.Certificate,
PrivateKey: d.option.Certificate.PrivateKey,
Variables: getDeployVariables(d.option.DeployConfig),
}
body, _ := json.Marshal(data)
resp, err := xhttp.Req(access.Url, http.MethodPost, bytes.NewReader(body), map[string]string{
"Content-Type": "application/json",
})
if err != nil {
return fmt.Errorf("failed to send hook request: %w", err)
}
d.infos = append(d.infos, toStr("webhook response", string(resp)))
return nil
}

View File

@@ -1,73 +1,217 @@
package domain
type AliyunAccess struct {
import (
"encoding/json"
"time"
)
const CollectionNameAccess = "access"
type Access struct {
Meta
Name string `json:"name" db:"name"`
Provider string `json:"provider" db:"provider"`
Config string `json:"config" db:"config"`
DeletedAt *time.Time `json:"deleted" db:"deleted"`
}
func (a *Access) UnmarshalConfigToMap() (map[string]any, error) {
config := make(map[string]any)
if err := json.Unmarshal([]byte(a.Config), &config); err != nil {
return nil, err
}
return config, nil
}
type AccessConfigFor1Panel struct {
ApiUrl string `json:"apiUrl"`
ApiKey string `json:"apiKey"`
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
}
type AccessConfigForACMEHttpReq struct {
Endpoint string `json:"endpoint"`
Mode string `json:"mode,omitempty"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
}
type AccessConfigForAliyun struct {
AccessKeyId string `json:"accessKeyId"`
AccessKeySecret string `json:"accessKeySecret"`
}
type TencentAccess struct {
SecretId string `json:"secretId"`
SecretKey string `json:"secretKey"`
}
type HuaweiCloudAccess struct {
Region string `json:"region"`
type AccessConfigForAWS struct {
AccessKeyId string `json:"accessKeyId"`
SecretAccessKey string `json:"secretAccessKey"`
}
type AwsAccess struct {
Region string `json:"region"`
type AccessConfigForAzure struct {
TenantId string `json:"tenantId"`
ClientId string `json:"clientId"`
ClientSecret string `json:"clientSecret"`
CloudName string `json:"cloudName,omitempty"`
}
type AccessConfigForBaiduCloud struct {
AccessKeyId string `json:"accessKeyId"`
SecretAccessKey string `json:"secretAccessKey"`
HostedZoneId string `json:"hostedZoneId"`
}
type CloudflareAccess struct {
DnsApiToken string `json:"dnsApiToken"`
type AccessConfigForBaishan struct {
ApiToken string `json:"apiToken"`
}
type QiniuAccess struct {
type AccessConfigForBaotaPanel struct {
ApiUrl string `json:"apiUrl"`
ApiKey string `json:"apiKey"`
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
}
type AccessConfigForBytePlus struct {
AccessKey string `json:"accessKey"`
SecretKey string `json:"secretKey"`
}
type NameSiloAccess struct {
ApiKey string `json:"apiKey"`
type AccessConfigForCacheFly struct {
ApiToken string `json:"apiToken"`
}
type GodaddyAccess struct {
type AccessConfigForCdnfly struct {
ApiUrl string `json:"apiUrl"`
ApiKey string `json:"apiKey"`
ApiSecret string `json:"apiSecret"`
}
type PdnsAccess struct {
type AccessConfigForCloudflare struct {
DnsApiToken string `json:"dnsApiToken"`
}
type AccessConfigForClouDNS struct {
AuthId string `json:"authId"`
AuthPassword string `json:"authPassword"`
}
type AccessConfigForCMCCCloud struct {
AccessKeyId string `json:"accessKeyId"`
AccessKeySecret string `json:"accessKeySecret"`
}
type AccessConfigForDNSLA struct {
ApiId string `json:"apiId"`
ApiSecret string `json:"apiSecret"`
}
type AccessConfigForDogeCloud struct {
AccessKey string `json:"accessKey"`
SecretKey string `json:"secretKey"`
}
type AccessConfigForEdgio struct {
ClientId string `json:"clientId"`
ClientSecret string `json:"clientSecret"`
}
type AccessConfigForGcore struct {
ApiToken string `json:"apiToken"`
}
type AccessConfigForGname struct {
AppId string `json:"appId"`
AppKey string `json:"appKey"`
}
type AccessConfigForGoDaddy struct {
ApiKey string `json:"apiKey"`
ApiSecret string `json:"apiSecret"`
}
type AccessConfigForHuaweiCloud struct {
AccessKeyId string `json:"accessKeyId"`
SecretAccessKey string `json:"secretAccessKey"`
}
type AccessConfigForJDCloud struct {
AccessKeyId string `json:"accessKeyId"`
AccessKeySecret string `json:"accessKeySecret"`
}
type AccessConfigForKubernetes struct {
KubeConfig string `json:"kubeConfig,omitempty"`
}
type AccessConfigForLocal struct{}
type AccessConfigForNamecheap struct {
Username string `json:"username"`
ApiKey string `json:"apiKey"`
}
type AccessConfigForNameDotCom struct {
Username string `json:"username"`
ApiToken string `json:"apiToken"`
}
type AccessConfigForNameSilo struct {
ApiKey string `json:"apiKey"`
}
type AccessConfigForNS1 struct {
ApiKey string `json:"apiKey"`
}
type AccessConfigForPowerDNS struct {
ApiUrl string `json:"apiUrl"`
ApiKey string `json:"apiKey"`
}
type HttpreqAccess struct {
Endpoint string `json:"endpoint"`
Mode string `json:"mode"`
Username string `json:"username"`
Password string `json:"password"`
type AccessConfigForQiniu struct {
AccessKey string `json:"accessKey"`
SecretKey string `json:"secretKey"`
}
type LocalAccess struct{}
type AccessConfigForRainYun struct {
ApiKey string `json:"apiKey"`
}
type SSHAccess struct {
type AccessConfigForSafeLine struct {
ApiUrl string `json:"apiUrl"`
ApiToken string `json:"apiToken"`
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
}
type AccessConfigForSSH struct {
Host string `json:"host"`
Port string `json:"port"`
Port int32 `json:"port"`
Username string `json:"username"`
Password string `json:"password"`
Key string `json:"key"`
KeyPassphrase string `json:"keyPassphrase"`
Password string `json:"password,omitempty"`
Key string `json:"key,omitempty"`
KeyPassphrase string `json:"keyPassphrase,omitempty"`
}
type WebhookAccess struct {
Url string `json:"url"`
type AccessConfigForTencentCloud struct {
SecretId string `json:"secretId"`
SecretKey string `json:"secretKey"`
}
type KubernetesAccess struct {
KubeConfig string `json:"kubeConfig"`
type AccessConfigForUCloud struct {
PrivateKey string `json:"privateKey"`
PublicKey string `json:"publicKey"`
ProjectId string `json:"projectId,omitempty"`
}
type AccessConfigForVolcEngine struct {
AccessKeyId string `json:"accessKeyId"`
SecretAccessKey string `json:"secretAccessKey"`
}
type AccessConfigForWebhook struct {
Url string `json:"url"`
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
}
type AccessConfigForWestcn struct {
Username string `json:"username"`
ApiPassword string `json:"password"`
}

View File

@@ -0,0 +1,15 @@
package domain
import (
"github.com/go-acme/lego/v4/registration"
)
const CollectionNameAcmeAccount = "acme_accounts"
type AcmeAccount struct {
Meta
CA string `json:"ca" db:"ca"`
Email string `json:"email" db:"email"`
Resource *registration.Resource `json:"resource" db:"resource"`
Key string `json:"key" db:"key"`
}

View File

@@ -0,0 +1,94 @@
package domain
import (
"crypto/x509"
"strings"
"time"
"github.com/usual2970/certimate/internal/pkg/utils/certs"
)
const CollectionNameCertificate = "certificate"
type Certificate struct {
Meta
Source CertificateSourceType `json:"source" db:"source"`
SubjectAltNames string `json:"subjectAltNames" db:"subjectAltNames"`
SerialNumber string `json:"serialNumber" db:"serialNumber"`
Certificate string `json:"certificate" db:"certificate"`
PrivateKey string `json:"privateKey" db:"privateKey"`
Issuer string `json:"issuer" db:"issuer"`
IssuerCertificate string `json:"issuerCertificate" db:"issuerCertificate"`
KeyAlgorithm CertificateKeyAlgorithmType `json:"keyAlgorithm" db:"keyAlgorithm"`
EffectAt time.Time `json:"effectAt" db:"effectAt"`
ExpireAt time.Time `json:"expireAt" db:"expireAt"`
ACMEAccountUrl string `json:"acmeAccountUrl" db:"acmeAccountUrl"`
ACMECertUrl string `json:"acmeCertUrl" db:"acmeCertUrl"`
ACMECertStableUrl string `json:"acmeCertStableUrl" db:"acmeCertStableUrl"`
WorkflowId string `json:"workflowId" db:"workflowId"`
WorkflowNodeId string `json:"workflowNodeId" db:"workflowNodeId"`
WorkflowRunId string `json:"workflowRunId" db:"workflowRunId"`
WorkflowOutputId string `json:"workflowOutputId" db:"workflowOutputId"`
DeletedAt *time.Time `json:"deleted" db:"deleted"`
}
func (c *Certificate) PopulateFromX509(certX509 *x509.Certificate) *Certificate {
c.SubjectAltNames = strings.Join(certX509.DNSNames, ";")
c.SerialNumber = strings.ToUpper(certX509.SerialNumber.Text(16))
c.Issuer = strings.Join(certX509.Issuer.Organization, ";")
c.EffectAt = certX509.NotBefore
c.ExpireAt = certX509.NotAfter
switch certX509.SignatureAlgorithm {
case x509.SHA256WithRSA, x509.SHA256WithRSAPSS:
c.KeyAlgorithm = CertificateKeyAlgorithmTypeRSA2048
case x509.SHA384WithRSA, x509.SHA384WithRSAPSS:
c.KeyAlgorithm = CertificateKeyAlgorithmTypeRSA3072
case x509.SHA512WithRSA, x509.SHA512WithRSAPSS:
c.KeyAlgorithm = CertificateKeyAlgorithmTypeRSA4096
case x509.ECDSAWithSHA256:
c.KeyAlgorithm = CertificateKeyAlgorithmTypeEC256
case x509.ECDSAWithSHA384:
c.KeyAlgorithm = CertificateKeyAlgorithmTypeEC384
case x509.ECDSAWithSHA512:
c.KeyAlgorithm = CertificateKeyAlgorithmTypeEC512
default:
c.KeyAlgorithm = CertificateKeyAlgorithmType("")
}
return c
}
func (c *Certificate) PopulateFromPEM(certPEM, privkeyPEM string) *Certificate {
c.Certificate = certPEM
c.PrivateKey = privkeyPEM
_, issuerCertPEM, _ := certs.ExtractCertificatesFromPEM(certPEM)
c.IssuerCertificate = issuerCertPEM
certX509, _ := certs.ParseCertificateFromPEM(certPEM)
if certX509 != nil {
c.PopulateFromX509(certX509)
}
return c
}
type CertificateSourceType string
const (
CertificateSourceTypeWorkflow = CertificateSourceType("workflow")
CertificateSourceTypeUpload = CertificateSourceType("upload")
)
type CertificateKeyAlgorithmType string
const (
CertificateKeyAlgorithmTypeRSA2048 = CertificateKeyAlgorithmType("RSA2048")
CertificateKeyAlgorithmTypeRSA3072 = CertificateKeyAlgorithmType("RSA3072")
CertificateKeyAlgorithmTypeRSA4096 = CertificateKeyAlgorithmType("RSA4096")
CertificateKeyAlgorithmTypeRSA8192 = CertificateKeyAlgorithmType("RSA8192")
CertificateKeyAlgorithmTypeEC256 = CertificateKeyAlgorithmType("EC256")
CertificateKeyAlgorithmTypeEC384 = CertificateKeyAlgorithmType("EC384")
CertificateKeyAlgorithmTypeEC512 = CertificateKeyAlgorithmType("EC512")
)

View File

@@ -1,110 +0,0 @@
package domain
import "strings"
type ApplyConfig struct {
Email string `json:"email"`
Access string `json:"access"`
KeyAlgorithm string `json:"keyAlgorithm"`
Nameservers string `json:"nameservers"`
Timeout int64 `json:"timeout"`
DisableFollowCNAME bool `json:"disableFollowCNAME"`
}
type DeployConfig struct {
Id string `json:"id"`
Access string `json:"access"`
Type string `json:"type"`
Config map[string]any `json:"config"`
}
// 以字符串形式获取配置项。
//
// 入参:
// - key: 配置项的键。
//
// 出参:
// - 配置项的值。如果配置项不存在或者类型不是字符串,则返回空字符串。
func (dc *DeployConfig) GetConfigAsString(key string) string {
return dc.GetConfigOrDefaultAsString(key, "")
}
// 以字符串形式获取配置项。
//
// 入参:
// - key: 配置项的键。
// - defaultValue: 默认值。
//
// 出参:
// - 配置项的值。如果配置项不存在或者类型不是字符串,则返回默认值。
func (dc *DeployConfig) GetConfigOrDefaultAsString(key string, defaultValue string) string {
if dc.Config == nil {
return defaultValue
}
if value, ok := dc.Config[key]; ok {
if result, ok := value.(string); ok {
return result
}
}
return defaultValue
}
// 以布尔形式获取配置项。
//
// 入参:
// - key: 配置项的键。
//
// 出参:
// - 配置项的值。如果配置项不存在或者类型不是布尔,则返回 false。
func (dc *DeployConfig) GetConfigAsBool(key string) bool {
return dc.GetConfigOrDefaultAsBool(key, false)
}
// 以布尔形式获取配置项。
//
// 入参:
// - key: 配置项的键。
// - defaultValue: 默认值。
//
// 出参:
// - 配置项的值。如果配置项不存在或者类型不是布尔,则返回默认值。
func (dc *DeployConfig) GetConfigOrDefaultAsBool(key string, defaultValue bool) bool {
if dc.Config == nil {
return defaultValue
}
if value, ok := dc.Config[key]; ok {
if result, ok := value.(bool); ok {
return result
}
}
return defaultValue
}
// GetDomain returns the domain from the deploy config
// if the domain is a wildcard domain, and wildcard is true, return the wildcard domain
func (dc *DeployConfig) GetDomain(wildcard ...bool) string {
val := dc.GetConfigAsString("domain")
if val == "" {
return ""
}
if !strings.HasPrefix(val, "*") {
return val
}
if len(wildcard) > 0 && wildcard[0] {
return val
}
return strings.TrimPrefix(val, "*")
}
type KV struct {
Key string `json:"key"`
Value string `json:"value"`
}

View File

@@ -0,0 +1,28 @@
package dtos
type CertificateArchiveFileReq struct {
CertificateId string `json:"-"`
Format string `json:"format"`
}
type CertificateArchiveFileResp struct {
FileBytes []byte `json:"fileBytes"`
FileFormat string `json:"fileFormat"`
}
type CertificateValidateCertificateReq struct {
Certificate string `json:"certificate"`
}
type CertificateValidateCertificateResp struct {
IsValid bool `json:"isValid"`
Domains string `json:"domains,omitempty"`
}
type CertificateValidatePrivateKeyReq struct {
PrivateKey string `json:"privateKey"`
}
type CertificateValidatePrivateKeyResp struct {
IsValid bool `json:"isValid"`
}

View File

@@ -0,0 +1,7 @@
package dtos
import "github.com/usual2970/certimate/internal/domain"
type NotifyTestPushReq struct {
Channel domain.NotifyChannelType `json:"channel"`
}

View File

@@ -0,0 +1,13 @@
package dtos
import "github.com/usual2970/certimate/internal/domain"
type WorkflowStartRunReq struct {
WorkflowId string `json:"-"`
RunTrigger domain.WorkflowTriggerType `json:"trigger"`
}
type WorkflowCancelRunReq struct {
WorkflowId string `json:"-"`
RunId string `json:"-"`
}

View File

@@ -1,23 +0,0 @@
package domain
var ErrAuthFailed = NewXError(4999, "auth failed")
type XError struct {
Code int `json:"code"`
Msg string `json:"msg"`
}
func NewXError(code int, msg string) *XError {
return &XError{code, msg}
}
func (e *XError) Error() string {
return e.Msg
}
func (e *XError) GetCode() int {
if e.Code == 0 {
return 100
}
return e.Code
}

30
internal/domain/error.go Normal file
View File

@@ -0,0 +1,30 @@
package domain
var (
ErrInvalidParams = NewError(400, "invalid params")
ErrRecordNotFound = NewError(404, "record not found")
)
type Error struct {
Code int `json:"code"`
Msg string `json:"msg"`
}
func NewError(code int, msg string) *Error {
if code == 0 {
code = -1
}
return &Error{code, msg}
}
func (e *Error) Error() string {
return e.Msg
}
func IsRecordNotFoundError(err error) bool {
if e, ok := err.(*Error); ok {
return e.Code == ErrRecordNotFound.Code
}
return false
}

9
internal/domain/meta.go Normal file
View File

@@ -0,0 +1,9 @@
package domain
import "time"
type Meta struct {
Id string `json:"id" db:"id"`
CreatedAt time.Time `json:"created" db:"created"`
UpdatedAt time.Time `json:"updated" db:"updated"`
}

View File

@@ -1,12 +1,20 @@
package domain
const (
NotifyChannelDingtalk = "dingtalk"
NotifyChannelWebhook = "webhook"
NotifyChannelTelegram = "telegram"
NotifyChannelLark = "lark"
)
type NotifyChannelType string
type NotifyTestPushReq struct {
Channel string `json:"channel"`
}
/*
消息通知渠道常量值。
注意:如果追加新的常量值,请保持以 ASCII 排序。
NOTICE: If you add new constant, please keep ASCII order.
*/
const (
NotifyChannelTypeBark = NotifyChannelType("bark")
NotifyChannelTypeDingTalk = NotifyChannelType("dingtalk")
NotifyChannelTypeEmail = NotifyChannelType("email")
NotifyChannelTypeLark = NotifyChannelType("lark")
NotifyChannelTypeServerChan = NotifyChannelType("serverchan")
NotifyChannelTypeTelegram = NotifyChannelType("telegram")
NotifyChannelTypeWebhook = NotifyChannelType("webhook")
NotifyChannelTypeWeCom = NotifyChannelType("wecom")
)

168
internal/domain/provider.go Normal file
View File

@@ -0,0 +1,168 @@
package domain
type AccessProviderType string
/*
授权提供商类型常量值。
注意:如果追加新的常量值,请保持以 ASCII 排序。
NOTICE: If you add new constant, please keep ASCII order.
*/
const (
AccessProviderType1Panel = AccessProviderType("1panel")
AccessProviderTypeACMEHttpReq = AccessProviderType("acmehttpreq")
AccessProviderTypeAkamai = AccessProviderType("akamai") // Akamai预留
AccessProviderTypeAliyun = AccessProviderType("aliyun")
AccessProviderTypeAWS = AccessProviderType("aws")
AccessProviderTypeAzure = AccessProviderType("azure")
AccessProviderTypeBaiduCloud = AccessProviderType("baiducloud")
AccessProviderTypeBaishan = AccessProviderType("baishan")
AccessProviderTypeBaotaPanel = AccessProviderType("baotapanel")
AccessProviderTypeBytePlus = AccessProviderType("byteplus")
AccessProviderTypeCacheFly = AccessProviderType("cachefly")
AccessProviderTypeCdnfly = AccessProviderType("cdnfly")
AccessProviderTypeCloudflare = AccessProviderType("cloudflare")
AccessProviderTypeClouDNS = AccessProviderType("cloudns")
AccessProviderTypeCMCCCloud = AccessProviderType("cmcccloud")
AccessProviderTypeCTCCCloud = AccessProviderType("ctcccloud") // 联通云(预留)
AccessProviderTypeCUCCCloud = AccessProviderType("cucccloud") // 天翼云(预留)
AccessProviderTypeDNSLA = AccessProviderType("dnsla")
AccessProviderTypeDogeCloud = AccessProviderType("dogecloud")
AccessProviderTypeEdgio = AccessProviderType("edgio")
AccessProviderTypeFastly = AccessProviderType("fastly") // Fastly预留
AccessProviderTypeGname = AccessProviderType("gname")
AccessProviderTypeGcore = AccessProviderType("gcore")
AccessProviderTypeGoDaddy = AccessProviderType("godaddy")
AccessProviderTypeGoEdge = AccessProviderType("goedge") // GoEdge预留
AccessProviderTypeHuaweiCloud = AccessProviderType("huaweicloud")
AccessProviderTypeJDCloud = AccessProviderType("jdcloud")
AccessProviderTypeKubernetes = AccessProviderType("k8s")
AccessProviderTypeLocal = AccessProviderType("local")
AccessProviderTypeNamecheap = AccessProviderType("namecheap")
AccessProviderTypeNameDotCom = AccessProviderType("namedotcom")
AccessProviderTypeNameSilo = AccessProviderType("namesilo")
AccessProviderTypeNS1 = AccessProviderType("ns1")
AccessProviderTypePowerDNS = AccessProviderType("powerdns")
AccessProviderTypeQiniu = AccessProviderType("qiniu")
AccessProviderTypeQingCloud = AccessProviderType("qingcloud") // 青云(预留)
AccessProviderTypeRainYun = AccessProviderType("rainyun")
AccessProviderTypeSafeLine = AccessProviderType("safeline")
AccessProviderTypeSSH = AccessProviderType("ssh")
AccessProviderTypeTencentCloud = AccessProviderType("tencentcloud")
AccessProviderTypeUCloud = AccessProviderType("ucloud")
AccessProviderTypeVolcEngine = AccessProviderType("volcengine")
AccessProviderTypeWebhook = AccessProviderType("webhook")
AccessProviderTypeWestcn = AccessProviderType("westcn")
)
type ApplyDNSProviderType string
/*
申请证书 DNS 提供商常量值。
短横线前的部分始终等于授权提供商类型。
注意:如果追加新的常量值,请保持以 ASCII 排序。
NOTICE: If you add new constant, please keep ASCII order.
*/
const (
ApplyDNSProviderTypeACMEHttpReq = ApplyDNSProviderType("acmehttpreq")
ApplyDNSProviderTypeAliyun = ApplyDNSProviderType("aliyun") // 兼容旧值,等同于 [ApplyDNSProviderTypeAliyunDNS]
ApplyDNSProviderTypeAliyunDNS = ApplyDNSProviderType("aliyun-dns")
ApplyDNSProviderTypeAWS = ApplyDNSProviderType("aws") // 兼容旧值,等同于 [ApplyDNSProviderTypeAWSRoute53]
ApplyDNSProviderTypeAWSRoute53 = ApplyDNSProviderType("aws-route53")
ApplyDNSProviderTypeAzure = ApplyDNSProviderType("azure") // 兼容旧值,等同于 [ApplyDNSProviderTypeAzure]
ApplyDNSProviderTypeAzureDNS = ApplyDNSProviderType("azure-dns")
ApplyDNSProviderTypeBaiduCloud = ApplyDNSProviderType("baiducloud") // 兼容旧值,等同于 [ApplyDNSProviderTypeBaiduCloudDNS]
ApplyDNSProviderTypeBaiduCloudDNS = ApplyDNSProviderType("baiducloud-dns")
ApplyDNSProviderTypeCloudflare = ApplyDNSProviderType("cloudflare")
ApplyDNSProviderTypeClouDNS = ApplyDNSProviderType("cloudns")
ApplyDNSProviderTypeCMCCCloud = ApplyDNSProviderType("cmcccloud")
ApplyDNSProviderTypeDNSLA = ApplyDNSProviderType("dnsla")
ApplyDNSProviderTypeGcore = ApplyDNSProviderType("gcore")
ApplyDNSProviderTypeGname = ApplyDNSProviderType("gname")
ApplyDNSProviderTypeGoDaddy = ApplyDNSProviderType("godaddy")
ApplyDNSProviderTypeHuaweiCloud = ApplyDNSProviderType("huaweicloud") // 兼容旧值,等同于 [ApplyDNSProviderTypeHuaweiCloudDNS]
ApplyDNSProviderTypeHuaweiCloudDNS = ApplyDNSProviderType("huaweicloud-dns")
ApplyDNSProviderTypeJDCloud = ApplyDNSProviderType("jdcloud") // 兼容旧值,等同于 [ApplyDNSProviderTypeJDCloudDNS]
ApplyDNSProviderTypeJDCloudDNS = ApplyDNSProviderType("jdcloud-dns")
ApplyDNSProviderTypeNamecheap = ApplyDNSProviderType("namecheap")
ApplyDNSProviderTypeNameDotCom = ApplyDNSProviderType("namedotcom")
ApplyDNSProviderTypeNameSilo = ApplyDNSProviderType("namesilo")
ApplyDNSProviderTypeNS1 = ApplyDNSProviderType("ns1")
ApplyDNSProviderTypePowerDNS = ApplyDNSProviderType("powerdns")
ApplyDNSProviderTypeRainYun = ApplyDNSProviderType("rainyun")
ApplyDNSProviderTypeTencentCloud = ApplyDNSProviderType("tencentcloud") // 兼容旧值,等同于 [ApplyDNSProviderTypeTencentCloudDNS]
ApplyDNSProviderTypeTencentCloudDNS = ApplyDNSProviderType("tencentcloud-dns")
ApplyDNSProviderTypeVolcEngine = ApplyDNSProviderType("volcengine") // 兼容旧值,等同于 [ApplyDNSProviderTypeVolcEngineDNS]
ApplyDNSProviderTypeVolcEngineDNS = ApplyDNSProviderType("volcengine-dns")
ApplyDNSProviderTypeWestcn = ApplyDNSProviderType("westcn")
)
type DeployProviderType string
/*
部署目标提供商常量值。
短横线前的部分始终等于授权提供商类型。
注意:如果追加新的常量值,请保持以 ASCII 排序。
NOTICE: If you add new constant, please keep ASCII order.
*/
const (
DeployProviderType1PanelConsole = DeployProviderType("1panel-console")
DeployProviderType1PanelSite = DeployProviderType("1panel-site")
DeployProviderTypeAliyunALB = DeployProviderType("aliyun-alb")
DeployProviderTypeAliyunCASDeploy = DeployProviderType("aliyun-casdeploy")
DeployProviderTypeAliyunCDN = DeployProviderType("aliyun-cdn")
DeployProviderTypeAliyunCLB = DeployProviderType("aliyun-clb")
DeployProviderTypeAliyunDCDN = DeployProviderType("aliyun-dcdn")
DeployProviderTypeAliyunESA = DeployProviderType("aliyun-esa")
DeployProviderTypeAliyunFC = DeployProviderType("aliyun-fc")
DeployProviderTypeAliyunLive = DeployProviderType("aliyun-live")
DeployProviderTypeAliyunNLB = DeployProviderType("aliyun-nlb")
DeployProviderTypeAliyunOSS = DeployProviderType("aliyun-oss")
DeployProviderTypeAliyunVOD = DeployProviderType("aliyun-vod")
DeployProviderTypeAliyunWAF = DeployProviderType("aliyun-waf")
DeployProviderTypeAWSCloudFront = DeployProviderType("aws-cloudfront")
DeployProviderTypeBaiduCloudCDN = DeployProviderType("baiducloud-cdn")
DeployProviderTypeBaishanCDN = DeployProviderType("baishan-cdn")
DeployProviderTypeBaotaPanelConsole = DeployProviderType("baotapanel-console")
DeployProviderTypeBaotaPanelSite = DeployProviderType("baotapanel-site")
DeployProviderTypeBytePlusCDN = DeployProviderType("byteplus-cdn")
DeployProviderTypeCacheFly = DeployProviderType("cachefly")
DeployProviderTypeCdnfly = DeployProviderType("cdnfly")
DeployProviderTypeDogeCloudCDN = DeployProviderType("dogecloud-cdn")
DeployProviderTypeEdgioApplications = DeployProviderType("edgio-applications")
DeployProviderTypeGcoreCDN = DeployProviderType("gcore-cdn")
DeployProviderTypeHuaweiCloudCDN = DeployProviderType("huaweicloud-cdn")
DeployProviderTypeHuaweiCloudELB = DeployProviderType("huaweicloud-elb")
DeployProviderTypeHuaweiCloudWAF = DeployProviderType("huaweicloud-waf")
DeployProviderTypeJDCloudALB = DeployProviderType("jdcloud-alb")
DeployProviderTypeJDCloudCDN = DeployProviderType("jdcloud-cdn")
DeployProviderTypeJDCloudLive = DeployProviderType("jdcloud-live")
DeployProviderTypeJDCloudVOD = DeployProviderType("jdcloud-vod")
DeployProviderTypeKubernetesSecret = DeployProviderType("k8s-secret")
DeployProviderTypeLocal = DeployProviderType("local")
DeployProviderTypeQiniuCDN = DeployProviderType("qiniu-cdn")
DeployProviderTypeQiniuPili = DeployProviderType("qiniu-pili")
DeployProviderTypeSafeLine = DeployProviderType("safeline")
DeployProviderTypeSSH = DeployProviderType("ssh")
DeployProviderTypeTencentCloudCDN = DeployProviderType("tencentcloud-cdn")
DeployProviderTypeTencentCloudCLB = DeployProviderType("tencentcloud-clb")
DeployProviderTypeTencentCloudCOS = DeployProviderType("tencentcloud-cos")
DeployProviderTypeTencentCloudCSS = DeployProviderType("tencentcloud-css")
DeployProviderTypeTencentCloudECDN = DeployProviderType("tencentcloud-ecdn")
DeployProviderTypeTencentCloudEO = DeployProviderType("tencentcloud-eo")
DeployProviderTypeTencentCloudSCF = DeployProviderType("tencentcloud-scf")
DeployProviderTypeTencentCloudSSLDeploy = DeployProviderType("tencentcloud-ssldeploy")
DeployProviderTypeTencentCloudVOD = DeployProviderType("tencentcloud-vod")
DeployProviderTypeTencentCloudWAF = DeployProviderType("tencentcloud-waf")
DeployProviderTypeUCloudUCDN = DeployProviderType("ucloud-ucdn")
DeployProviderTypeUCloudUS3 = DeployProviderType("ucloud-us3")
DeployProviderTypeVolcEngineCDN = DeployProviderType("volcengine-cdn")
DeployProviderTypeVolcEngineCLB = DeployProviderType("volcengine-clb")
DeployProviderTypeVolcEngineDCDN = DeployProviderType("volcengine-dcdn")
DeployProviderTypeVolcEngineImageX = DeployProviderType("volcengine-imagex")
DeployProviderTypeVolcEngineLive = DeployProviderType("volcengine-live")
DeployProviderTypeVolcEngineTOS = DeployProviderType("volcengine-tos")
DeployProviderTypeWebhook = DeployProviderType("webhook")
)

View File

@@ -1,31 +0,0 @@
package domain
import (
"encoding/json"
"fmt"
"time"
)
type Setting struct {
ID string `json:"id"`
Name string `json:"name"`
Content string `json:"content"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
}
type ChannelsConfig map[string]map[string]any
func (s *Setting) GetChannelContent(channel string) (map[string]any, error) {
conf := &ChannelsConfig{}
if err := json.Unmarshal([]byte(s.Content), conf); err != nil {
return nil, err
}
v, ok := (*conf)[channel]
if !ok {
return nil, fmt.Errorf("channel %s not found", channel)
}
return v, nil
}

View File

@@ -0,0 +1,39 @@
package domain
import (
"encoding/json"
"fmt"
)
const CollectionNameSettings = "settings"
type Settings struct {
Meta
Name string `json:"name" db:"name"`
Content string `json:"content" db:"content"`
}
type NotifyTemplatesSettingsContent struct {
NotifyTemplates []NotifyTemplate `json:"notifyTemplates"`
}
type NotifyTemplate struct {
Subject string `json:"subject"`
Message string `json:"message"`
}
type NotifyChannelsSettingsContent map[string]map[string]any
func (s *Settings) GetNotifyChannelConfig(channel string) (map[string]any, error) {
conf := &NotifyChannelsSettingsContent{}
if err := json.Unmarshal([]byte(s.Content), conf); err != nil {
return nil, err
}
v, ok := (*conf)[channel]
if !ok {
return nil, fmt.Errorf("channel \"%s\" not found", channel)
}
return v, nil
}

View File

@@ -0,0 +1,11 @@
package domain
type Statistics struct {
CertificateTotal int `json:"certificateTotal"`
CertificateExpireSoon int `json:"certificateExpireSoon"`
CertificateExpired int `json:"certificateExpired"`
WorkflowTotal int `json:"workflowTotal"`
WorkflowEnabled int `json:"workflowEnabled"`
WorkflowDisabled int `json:"workflowDisabled"`
}

184
internal/domain/workflow.go Normal file
View File

@@ -0,0 +1,184 @@
package domain
import (
"time"
"github.com/usual2970/certimate/internal/pkg/utils/maps"
)
const CollectionNameWorkflow = "workflow"
type Workflow struct {
Meta
Name string `json:"name" db:"name"`
Description string `json:"description" db:"description"`
Trigger WorkflowTriggerType `json:"trigger" db:"trigger"`
TriggerCron string `json:"triggerCron" db:"triggerCron"`
Enabled bool `json:"enabled" db:"enabled"`
Content *WorkflowNode `json:"content" db:"content"`
Draft *WorkflowNode `json:"draft" db:"draft"`
HasDraft bool `json:"hasDraft" db:"hasDraft"`
LastRunId string `json:"lastRunId" db:"lastRunId"`
LastRunStatus WorkflowRunStatusType `json:"lastRunStatus" db:"lastRunStatus"`
LastRunTime time.Time `json:"lastRunTime" db:"lastRunTime"`
}
type WorkflowNodeType string
const (
WorkflowNodeTypeStart = WorkflowNodeType("start")
WorkflowNodeTypeEnd = WorkflowNodeType("end")
WorkflowNodeTypeApply = WorkflowNodeType("apply")
WorkflowNodeTypeUpload = WorkflowNodeType("upload")
WorkflowNodeTypeDeploy = WorkflowNodeType("deploy")
WorkflowNodeTypeNotify = WorkflowNodeType("notify")
WorkflowNodeTypeBranch = WorkflowNodeType("branch")
WorkflowNodeTypeCondition = WorkflowNodeType("condition")
WorkflowNodeTypeExecuteResultBranch = WorkflowNodeType("execute_result_branch")
WorkflowNodeTypeExecuteSuccess = WorkflowNodeType("execute_success")
WorkflowNodeTypeExecuteFailure = WorkflowNodeType("execute_failure")
)
type WorkflowTriggerType string
const (
WorkflowTriggerTypeAuto = WorkflowTriggerType("auto")
WorkflowTriggerTypeManual = WorkflowTriggerType("manual")
)
type WorkflowNode struct {
Id string `json:"id"`
Type WorkflowNodeType `json:"type"`
Name string `json:"name"`
Config map[string]any `json:"config"`
Inputs []WorkflowNodeIO `json:"inputs"`
Outputs []WorkflowNodeIO `json:"outputs"`
Next *WorkflowNode `json:"next,omitempty"`
Branches []WorkflowNode `json:"branches,omitempty"`
Validated bool `json:"validated"`
}
type WorkflowNodeConfigForApply struct {
Domains string `json:"domains"` // 域名列表,以半角逗号分隔
ContactEmail string `json:"contactEmail"` // 联系邮箱
ChallengeType string `json:"challengeType"` // TODO: 验证方式。目前仅支持 dns-01
Provider string `json:"provider"` // DNS 提供商
ProviderAccessId string `json:"providerAccessId"` // DNS 提供商授权记录 ID
ProviderConfig map[string]any `json:"providerConfig"` // DNS 提供商额外配置
KeyAlgorithm string `json:"keyAlgorithm"` // 密钥算法
Nameservers string `json:"nameservers"` // DNS 服务器列表,以半角逗号分隔
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout"` // DNS 传播超时时间(零值取决于提供商的默认值)
DnsTTL int32 `json:"dnsTTL"` // DNS TTL零值取决于提供商的默认值
DisableFollowCNAME bool `json:"disableFollowCNAME"` // 是否关闭 CNAME 跟随
DisableARI bool `json:"disableARI"` // 是否关闭 ARI
SkipBeforeExpiryDays int32 `json:"skipBeforeExpiryDays"` // 证书到期前多少天前跳过续期(零值将使用默认值 30
}
type WorkflowNodeConfigForUpload struct {
Certificate string `json:"certificate"`
PrivateKey string `json:"privateKey"`
Domains string `json:"domains"`
}
type WorkflowNodeConfigForDeploy struct {
Certificate string `json:"certificate"` // 前序节点输出的证书,形如“${NodeId}#certificate”
Provider string `json:"provider"` // 主机提供商
ProviderAccessId string `json:"providerAccessId"` // 主机提供商授权记录 ID
ProviderConfig map[string]any `json:"providerConfig"` // 主机提供商额外配置
SkipOnLastSucceeded bool `json:"skipOnLastSucceeded"` // 上次部署成功时是否跳过
}
type WorkflowNodeConfigForNotify struct {
Channel string `json:"channel"` // 通知渠道
Subject string `json:"subject"` // 通知主题
Message string `json:"message"` // 通知内容
}
func (n *WorkflowNode) getConfigValueAsString(key string) string {
return maps.GetValueAsString(n.Config, key)
}
func (n *WorkflowNode) getConfigValueAsBool(key string) bool {
return maps.GetValueAsBool(n.Config, key)
}
func (n *WorkflowNode) getConfigValueAsInt32(key string) int32 {
return maps.GetValueAsInt32(n.Config, key)
}
func (n *WorkflowNode) getConfigValueAsMap(key string) map[string]any {
if val, ok := n.Config[key]; ok {
if result, ok := val.(map[string]any); ok {
return result
}
}
return make(map[string]any)
}
func (n *WorkflowNode) GetConfigForApply() WorkflowNodeConfigForApply {
skipBeforeExpiryDays := n.getConfigValueAsInt32("skipBeforeExpiryDays")
if skipBeforeExpiryDays == 0 {
skipBeforeExpiryDays = 30
}
return WorkflowNodeConfigForApply{
Domains: n.getConfigValueAsString("domains"),
ContactEmail: n.getConfigValueAsString("contactEmail"),
Provider: n.getConfigValueAsString("provider"),
ProviderAccessId: n.getConfigValueAsString("providerAccessId"),
ProviderConfig: n.getConfigValueAsMap("providerConfig"),
KeyAlgorithm: n.getConfigValueAsString("keyAlgorithm"),
Nameservers: n.getConfigValueAsString("nameservers"),
DnsPropagationTimeout: n.getConfigValueAsInt32("dnsPropagationTimeout"),
DnsTTL: n.getConfigValueAsInt32("dnsTTL"),
DisableFollowCNAME: n.getConfigValueAsBool("disableFollowCNAME"),
DisableARI: n.getConfigValueAsBool("disableARI"),
SkipBeforeExpiryDays: skipBeforeExpiryDays,
}
}
func (n *WorkflowNode) GetConfigForUpload() WorkflowNodeConfigForUpload {
return WorkflowNodeConfigForUpload{
Certificate: n.getConfigValueAsString("certificate"),
PrivateKey: n.getConfigValueAsString("privateKey"),
Domains: n.getConfigValueAsString("domains"),
}
}
func (n *WorkflowNode) GetConfigForDeploy() WorkflowNodeConfigForDeploy {
return WorkflowNodeConfigForDeploy{
Certificate: n.getConfigValueAsString("certificate"),
Provider: n.getConfigValueAsString("provider"),
ProviderAccessId: n.getConfigValueAsString("providerAccessId"),
ProviderConfig: n.getConfigValueAsMap("providerConfig"),
SkipOnLastSucceeded: n.getConfigValueAsBool("skipOnLastSucceeded"),
}
}
func (n *WorkflowNode) GetConfigForNotify() WorkflowNodeConfigForNotify {
return WorkflowNodeConfigForNotify{
Channel: n.getConfigValueAsString("channel"),
Subject: n.getConfigValueAsString("subject"),
Message: n.getConfigValueAsString("message"),
}
}
type WorkflowNodeIO struct {
Label string `json:"label"`
Name string `json:"name"`
Type string `json:"type"`
Required bool `json:"required"`
Value any `json:"value"`
ValueSelector WorkflowNodeIOValueSelector `json:"valueSelector"`
}
type WorkflowNodeIOValueSelector struct {
Id string `json:"id"`
Name string `json:"name"`
}
const WorkflowNodeIONameCertificate string = "certificate"

View File

@@ -0,0 +1,13 @@
package domain
const CollectionNameWorkflowOutput = "workflow_output"
type WorkflowOutput struct {
Meta
WorkflowId string `json:"workflowId" db:"workflow"`
RunId string `json:"runId" db:"runId"`
NodeId string `json:"nodeId" db:"nodeId"`
Node *WorkflowNode `json:"node" db:"node"`
Outputs []WorkflowNodeIO `json:"outputs" db:"outputs"`
Succeeded bool `json:"succeeded" db:"succeeded"`
}

View File

@@ -0,0 +1,65 @@
package domain
import (
"strings"
"time"
)
const CollectionNameWorkflowRun = "workflow_run"
type WorkflowRun struct {
Meta
WorkflowId string `json:"workflowId" db:"workflowId"`
Status WorkflowRunStatusType `json:"status" db:"status"`
Trigger WorkflowTriggerType `json:"trigger" db:"trigger"`
StartedAt time.Time `json:"startedAt" db:"startedAt"`
EndedAt time.Time `json:"endedAt" db:"endedAt"`
Logs []WorkflowRunLog `json:"logs" db:"logs"`
Error string `json:"error" db:"error"`
}
type WorkflowRunStatusType string
const (
WorkflowRunStatusTypePending WorkflowRunStatusType = "pending"
WorkflowRunStatusTypeRunning WorkflowRunStatusType = "running"
WorkflowRunStatusTypeSucceeded WorkflowRunStatusType = "succeeded"
WorkflowRunStatusTypeFailed WorkflowRunStatusType = "failed"
WorkflowRunStatusTypeCanceled WorkflowRunStatusType = "canceled"
)
type WorkflowRunLog struct {
NodeId string `json:"nodeId"`
NodeName string `json:"nodeName"`
Records []WorkflowRunLogRecord `json:"records"`
Error string `json:"error"`
}
type WorkflowRunLogRecord struct {
Time string `json:"time"`
Level WorkflowRunLogLevel `json:"level"`
Content string `json:"content"`
Error string `json:"error"`
}
type WorkflowRunLogLevel string
const (
WorkflowRunLogLevelDebug WorkflowRunLogLevel = "DEBUG"
WorkflowRunLogLevelInfo WorkflowRunLogLevel = "INFO"
WorkflowRunLogLevelWarn WorkflowRunLogLevel = "WARN"
WorkflowRunLogLevelError WorkflowRunLogLevel = "ERROR"
)
type WorkflowRunLogs []WorkflowRunLog
func (r WorkflowRunLogs) ErrorString() string {
var builder strings.Builder
for _, log := range r {
if log.Error != "" {
builder.WriteString(log.Error)
builder.WriteString("\n")
}
}
return builder.String()
}

View File

@@ -1,123 +0,0 @@
package domains
import (
"context"
"fmt"
"time"
"github.com/pocketbase/pocketbase/models"
"github.com/usual2970/certimate/internal/applicant"
"github.com/usual2970/certimate/internal/deployer"
"github.com/usual2970/certimate/internal/utils/app"
)
type Phase string
const (
checkPhase Phase = "check"
applyPhase Phase = "apply"
deployPhase Phase = "deploy"
)
func deploy(ctx context.Context, record *models.Record) error {
defer func() {
if r := recover(); r != nil {
app.GetApp().Logger().Error("部署失败", "err", r)
}
}()
var certificate *applicant.Certificate
history := NewHistory(record)
defer history.commit()
// ############1.检查域名配置
history.record(checkPhase, "开始检查", nil)
currRecord, err := app.GetApp().Dao().FindRecordById("domains", record.Id)
if err != nil {
app.GetApp().Logger().Error("获取记录失败", "err", err)
history.record(checkPhase, "获取域名配置失败", &RecordInfo{Err: err})
return err
}
history.record(checkPhase, "获取记录成功", nil)
cert := currRecord.GetString("certificate")
expiredAt := currRecord.GetDateTime("expiredAt").Time()
if cert != "" && time.Until(expiredAt) > time.Hour*24*10 && currRecord.GetBool("deployed") {
app.GetApp().Logger().Info("证书在有效期内")
history.record(checkPhase, "证书在有效期内且已部署,跳过", &RecordInfo{
Info: []string{fmt.Sprintf("证书有效期至 %s", expiredAt.Format("2006-01-02"))},
}, true)
// 跳过的情况也算成功
history.setWholeSuccess(true)
return nil
}
history.record(checkPhase, "检查通过", nil, true)
// ############2.申请证书
history.record(applyPhase, "开始申请", nil)
if cert != "" && time.Until(expiredAt) > time.Hour*24 {
history.record(applyPhase, "证书在有效期内,跳过", &RecordInfo{
Info: []string{fmt.Sprintf("证书有效期至 %s", expiredAt.Format("2006-01-02"))},
})
} else {
applicant, err := applicant.Get(currRecord)
if err != nil {
history.record(applyPhase, "获取applicant失败", &RecordInfo{Err: err})
app.GetApp().Logger().Error("获取applicant失败", "err", err)
return err
}
certificate, err = applicant.Apply()
if err != nil {
history.record(applyPhase, "申请证书失败", &RecordInfo{Err: err})
app.GetApp().Logger().Error("申请证书失败", "err", err)
return err
}
history.record(applyPhase, "申请证书成功", &RecordInfo{
Info: []string{fmt.Sprintf("证书地址: %s", certificate.CertUrl)},
})
history.setCert(certificate)
}
history.record(applyPhase, "保存证书成功", nil, true)
// ############3.部署证书
history.record(deployPhase, "开始部署", nil, false)
deployers, err := deployer.Gets(currRecord, certificate)
if err != nil {
history.record(deployPhase, "获取deployer失败", &RecordInfo{Err: err})
app.GetApp().Logger().Error("获取deployer失败", "err", err)
return err
}
// 没有部署配置,也算成功
if len(deployers) == 0 {
history.record(deployPhase, "没有部署配置", &RecordInfo{Info: []string{"没有部署配置"}})
history.setWholeSuccess(true)
return nil
}
for _, deployer := range deployers {
if err = deployer.Deploy(ctx); err != nil {
app.GetApp().Logger().Error("部署失败", "err", err)
history.record(deployPhase, "部署失败", &RecordInfo{Err: err, Info: deployer.GetInfo()})
return err
}
history.record(deployPhase, fmt.Sprintf("[%s]-部署成功", deployer.GetID()), &RecordInfo{
Info: deployer.GetInfo(),
}, false)
}
app.GetApp().Logger().Info("部署成功")
history.record(deployPhase, "部署成功", nil, true)
history.setWholeSuccess(true)
return nil
}

View File

@@ -1,79 +0,0 @@
package domains
import (
"context"
"fmt"
"github.com/pocketbase/pocketbase/models"
"github.com/usual2970/certimate/internal/utils/app"
)
func create(ctx context.Context, record *models.Record) error {
if !record.GetBool("enabled") {
return nil
}
if record.GetBool("rightnow") {
go func() {
if err := deploy(ctx, record); err != nil {
app.GetApp().Logger().Error("deploy failed", "err", err)
}
}()
}
scheduler := app.GetScheduler()
err := scheduler.Add(record.Id, record.GetString("crontab"), func() {
deploy(ctx, record)
})
if err != nil {
app.GetApp().Logger().Error("add cron job failed", "err", err)
return fmt.Errorf("add cron job failed: %w", err)
}
app.GetApp().Logger().Error("add cron job failed", "domain", record.GetString("domain"))
scheduler.Start()
return nil
}
func update(ctx context.Context, record *models.Record) error {
scheduler := app.GetScheduler()
scheduler.Remove(record.Id)
if !record.GetBool("enabled") {
return nil
}
if record.GetBool("rightnow") {
go func() {
if err := deploy(ctx, record); err != nil {
app.GetApp().Logger().Error("deploy failed", "err", err)
}
}()
}
err := scheduler.Add(record.Id, record.GetString("crontab"), func() {
deploy(ctx, record)
})
if err != nil {
app.GetApp().Logger().Error("update cron job failed", "err", err)
return fmt.Errorf("update cron job failed: %w", err)
}
app.GetApp().Logger().Info("update cron job success", "domain", record.GetString("domain"))
scheduler.Start()
return nil
}
func delete(_ context.Context, record *models.Record) error {
scheduler := app.GetScheduler()
scheduler.Remove(record.Id)
scheduler.Start()
return nil
}
func setRightnow(ctx context.Context, record *models.Record, ok bool) error {
record.Set("rightnow", ok)
return app.GetApp().Dao().SaveRecord(record)
}

View File

@@ -1,27 +0,0 @@
package domains
import (
"github.com/pocketbase/pocketbase/core"
"github.com/usual2970/certimate/internal/utils/app"
)
const tableName = "domains"
func AddEvent() error {
app := app.GetApp()
app.OnRecordAfterCreateRequest(tableName).Add(func(e *core.RecordCreateEvent) error {
return create(e.HttpContext.Request().Context(), e.Record)
})
app.OnRecordAfterUpdateRequest(tableName).Add(func(e *core.RecordUpdateEvent) error {
return update(e.HttpContext.Request().Context(), e.Record)
})
app.OnRecordAfterDeleteRequest(tableName).Add(func(e *core.RecordDeleteEvent) error {
return delete(e.HttpContext.Request().Context(), e.Record)
})
return nil
}

View File

@@ -1,122 +0,0 @@
package domains
import (
"time"
"github.com/pocketbase/pocketbase/models"
"github.com/usual2970/certimate/internal/applicant"
"github.com/usual2970/certimate/internal/utils/app"
"github.com/usual2970/certimate/internal/utils/xtime"
)
type historyItem struct {
Time string `json:"time"`
Message string `json:"message"`
Error string `json:"error"`
Info []string `json:"info"`
}
type RecordInfo struct {
Err error `json:"err"`
Info []string `json:"info"`
}
type history struct {
Domain string `json:"domain"`
Log map[Phase][]historyItem `json:"log"`
Phase Phase `json:"phase"`
PhaseSuccess bool `json:"phaseSuccess"`
DeployedAt string `json:"deployedAt"`
Cert *applicant.Certificate `json:"cert"`
WholeSuccess bool `json:"wholeSuccess"`
}
func NewHistory(record *models.Record) *history {
return &history{
Domain: record.Id,
DeployedAt: time.Now().UTC().Format("2006-01-02T15:04:05Z"),
Log: make(map[Phase][]historyItem),
Phase: checkPhase,
PhaseSuccess: false,
}
}
func (a *history) record(phase Phase, msg string, info *RecordInfo, pass ...bool) {
if info == nil {
info = &RecordInfo{}
}
a.Phase = phase
if len(pass) > 0 {
a.PhaseSuccess = pass[0]
}
errMsg := ""
if info.Err != nil {
errMsg = info.Err.Error()
a.PhaseSuccess = false
}
a.Log[phase] = append(a.Log[phase], historyItem{
Message: msg,
Error: errMsg,
Info: info.Info,
Time: xtime.BeijingTimeStr(),
})
}
func (a *history) setCert(cert *applicant.Certificate) {
a.Cert = cert
}
func (a *history) setWholeSuccess(success bool) {
a.WholeSuccess = success
}
func (a *history) commit() error {
collection, err := app.GetApp().Dao().FindCollectionByNameOrId("deployments")
if err != nil {
return err
}
record := models.NewRecord(collection)
record.Set("domain", a.Domain)
record.Set("deployedAt", a.DeployedAt)
record.Set("log", a.Log)
record.Set("phase", string(a.Phase))
record.Set("phaseSuccess", a.PhaseSuccess)
record.Set("wholeSuccess", a.WholeSuccess)
if err := app.GetApp().Dao().SaveRecord(record); err != nil {
return err
}
domainRecord, err := app.GetApp().Dao().FindRecordById("domains", a.Domain)
if err != nil {
return err
}
domainRecord.Set("lastDeployedAt", a.DeployedAt)
domainRecord.Set("lastDeployment", record.Id)
domainRecord.Set("rightnow", false)
if a.Phase == deployPhase && a.PhaseSuccess {
domainRecord.Set("deployed", true)
}
cert := a.Cert
if cert != nil {
domainRecord.Set("certUrl", cert.CertUrl)
domainRecord.Set("certStableUrl", cert.CertStableUrl)
domainRecord.Set("privateKey", cert.PrivateKey)
domainRecord.Set("certificate", cert.Certificate)
domainRecord.Set("issuerCertificate", cert.IssuerCertificate)
domainRecord.Set("csr", cert.Csr)
domainRecord.Set("expiredAt", time.Now().Add(time.Hour*24*90))
}
if err := app.GetApp().Dao().SaveRecord(domainRecord); err != nil {
return err
}
return nil
}

View File

@@ -1,38 +0,0 @@
package domains
import (
"context"
"github.com/usual2970/certimate/internal/notify"
"github.com/usual2970/certimate/internal/utils/app"
)
func InitSchedule() {
// 查询所有启用的域名
records, err := app.GetApp().Dao().FindRecordsByFilter("domains", "enabled=true", "-id", 500, 0)
if err != nil {
app.GetApp().Logger().Error("查询所有启用的域名失败", "err", err)
return
}
// 加入到定时任务
for _, record := range records {
if err := app.GetScheduler().Add(record.Id, record.GetString("crontab"), func() {
if err := deploy(context.Background(), record); err != nil {
app.GetApp().Logger().Error("部署失败", "err", err)
return
}
}); err != nil {
app.GetApp().Logger().Error("加入到定时任务失败", "err", err)
}
}
// 过期提醒
app.GetScheduler().Add("expire", "0 0 * * *", func() {
notify.PushExpireMsg()
})
// 启动定时任务
app.GetScheduler().Start()
app.GetApp().Logger().Info("定时任务启动成功", "total", app.GetScheduler().Total())
}

View File

@@ -1,97 +0,0 @@
package notify
import (
"strconv"
"strings"
"time"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/models"
"github.com/usual2970/certimate/internal/utils/app"
"github.com/usual2970/certimate/internal/utils/xtime"
)
type msg struct {
subject string
message string
}
const (
defaultExpireSubject = "您有{COUNT}张证书即将过期"
defaultExpireMsg = "有{COUNT}张证书即将过期,域名分别为{DOMAINS},请保持关注!"
)
func PushExpireMsg() {
// 查询即将过期的证书
records, err := app.GetApp().Dao().FindRecordsByFilter("domains", "expiredAt<{:time}&&certUrl!=''", "-created", 500, 0,
dbx.Params{"time": xtime.GetTimeAfter(24 * time.Hour * 15)})
if err != nil {
app.GetApp().Logger().Error("find expired domains by filter", "error", err)
return
}
// 组装消息
msg := buildMsg(records)
if msg == nil {
return
}
if err := Send(msg.subject, msg.message); err != nil {
app.GetApp().Logger().Error("send expire msg", "error", err)
}
}
type notifyTemplates struct {
NotifyTemplates []notifyTemplate `json:"notifyTemplates"`
}
type notifyTemplate struct {
Title string `json:"title"`
Content string `json:"content"`
}
func buildMsg(records []*models.Record) *msg {
if len(records) == 0 {
return nil
}
// 查询模板信息
templateRecord, err := app.GetApp().Dao().FindFirstRecordByFilter("settings", "name='templates'")
title := defaultExpireSubject
content := defaultExpireMsg
if err == nil {
var templates *notifyTemplates
templateRecord.UnmarshalJSONField("content", templates)
if templates != nil && len(templates.NotifyTemplates) > 0 {
title = templates.NotifyTemplates[0].Title
content = templates.NotifyTemplates[0].Content
}
}
// 替换变量
count := len(records)
domains := make([]string, count)
for i, record := range records {
domains[i] = record.GetString("domain")
}
countStr := strconv.Itoa(count)
domainStr := strings.Join(domains, ",")
title = strings.ReplaceAll(title, "{COUNT}", countStr)
title = strings.ReplaceAll(title, "{DOMAINS}", domainStr)
content = strings.ReplaceAll(content, "{COUNT}", countStr)
content = strings.ReplaceAll(content, "{DOMAINS}", domainStr)
// 返回消息
return &msg{
subject: title,
message: content,
}
}

View File

@@ -2,22 +2,19 @@ package notify
import (
"context"
"encoding/json"
"fmt"
"strconv"
"golang.org/x/sync/errgroup"
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/utils/app"
notifyPackage "github.com/nikoksr/notify"
"github.com/nikoksr/notify/service/dingding"
"github.com/nikoksr/notify/service/http"
"github.com/nikoksr/notify/service/lark"
"github.com/nikoksr/notify/service/telegram"
"github.com/usual2970/certimate/internal/pkg/core/notifier"
"github.com/usual2970/certimate/internal/pkg/utils/maps"
"github.com/usual2970/certimate/internal/repository"
)
func Send(title, content string) error {
// 获取所有的推送渠道
notifiers, err := getNotifiers()
func SendToAllChannels(subject, message string) error {
notifiers, err := getEnabledNotifiers()
if err != nil {
return err
}
@@ -25,136 +22,57 @@ func Send(title, content string) error {
return nil
}
n := notifyPackage.New()
// 添加推送渠道
n.UseServices(notifiers...)
var eg errgroup.Group
for _, n := range notifiers {
if n == nil {
continue
}
// 发送消息
return n.Send(context.Background(), title, content)
eg.Go(func() error {
_, err := n.Notify(context.Background(), subject, message)
return err
})
}
err = eg.Wait()
return err
}
type sendTestParam struct {
Title string `json:"title"`
Content string `json:"content"`
Channel string `json:"channel"`
Conf map[string]any `json:"conf"`
}
func SendTest(param *sendTestParam) error {
notifier, err := getNotifier(param.Channel, param.Conf)
func SendToChannel(subject, message string, channel string, channelConfig map[string]any) error {
notifier, err := createNotifier(domain.NotifyChannelType(channel), channelConfig)
if err != nil {
return err
}
n := notifyPackage.New()
// 添加推送渠道
n.UseServices(notifier)
// 发送消息
return n.Send(context.Background(), param.Title, param.Content)
_, err = notifier.Notify(context.Background(), subject, message)
return err
}
func getNotifiers() ([]notifyPackage.Notifier, error) {
resp, err := app.GetApp().Dao().FindFirstRecordByFilter("settings", "name='notifyChannels'")
func getEnabledNotifiers() ([]notifier.Notifier, error) {
settingsRepo := repository.NewSettingsRepository()
settings, err := settingsRepo.GetByName(context.Background(), "notifyChannels")
if err != nil {
return nil, fmt.Errorf("find notifyChannels error: %w", err)
}
notifiers := make([]notifyPackage.Notifier, 0)
rs := make(map[string]map[string]any)
if err := resp.UnmarshalJSONField("content", &rs); err != nil {
if err := json.Unmarshal([]byte(settings.Content), &rs); err != nil {
return nil, fmt.Errorf("unmarshal notifyChannels error: %w", err)
}
notifiers := make([]notifier.Notifier, 0)
for k, v := range rs {
if !getBool(v, "enabled") {
if !maps.GetValueAsBool(v, "enabled") {
continue
}
notifier, err := getNotifier(k, v)
notifier, err := createNotifier(domain.NotifyChannelType(k), v)
if err != nil {
continue
}
notifiers = append(notifiers, notifier)
}
return notifiers, nil
}
func getNotifier(channel string, conf map[string]any) (notifyPackage.Notifier, error) {
switch channel {
case domain.NotifyChannelTelegram:
temp := getTelegramNotifier(conf)
if temp == nil {
return nil, fmt.Errorf("telegram notifier config error")
}
return temp, nil
case domain.NotifyChannelDingtalk:
return getDingTalkNotifier(conf), nil
case domain.NotifyChannelLark:
return getLarkNotifier(conf), nil
case domain.NotifyChannelWebhook:
return getWebhookNotifier(conf), nil
}
return nil, fmt.Errorf("notifier not found")
}
func getWebhookNotifier(conf map[string]any) notifyPackage.Notifier {
rs := http.New()
rs.AddReceiversURLs(getString(conf, "url"))
return rs
}
func getTelegramNotifier(conf map[string]any) notifyPackage.Notifier {
rs, err := telegram.New(getString(conf, "apiToken"))
if err != nil {
return nil
}
chatId := getString(conf, "chatId")
id, err := strconv.ParseInt(chatId, 10, 64)
if err != nil {
return nil
}
rs.AddReceivers(id)
return rs
}
func getDingTalkNotifier(conf map[string]any) notifyPackage.Notifier {
return dingding.New(&dingding.Config{
Token: getString(conf, "accessToken"),
Secret: getString(conf, "secret"),
})
}
func getLarkNotifier(conf map[string]any) notifyPackage.Notifier {
return lark.NewWebhookService(getString(conf, "webhookUrl"))
}
func getString(conf map[string]any, key string) string {
if _, ok := conf[key]; !ok {
return ""
}
return conf[key].(string)
}
func getBool(conf map[string]any, key string) bool {
if _, ok := conf[key]; !ok {
return false
}
return conf[key].(bool)
}

View File

@@ -0,0 +1,77 @@
package notify
import (
"fmt"
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/pkg/core/notifier"
pBark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/bark"
pDingTalk "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/dingtalk"
pEmail "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/email"
pLark "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/lark"
pServerChan "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/serverchan"
pTelegram "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/telegram"
pWebhook "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/webhook"
pWeCom "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/wecom"
"github.com/usual2970/certimate/internal/pkg/utils/maps"
)
func createNotifier(channel domain.NotifyChannelType, channelConfig map[string]any) (notifier.Notifier, error) {
/*
注意:如果追加新的常量值,请保持以 ASCII 排序。
NOTICE: If you add new constant, please keep ASCII order.
*/
switch channel {
case domain.NotifyChannelTypeBark:
return pBark.NewNotifier(&pBark.NotifierConfig{
DeviceKey: maps.GetValueAsString(channelConfig, "deviceKey"),
ServerUrl: maps.GetValueAsString(channelConfig, "serverUrl"),
})
case domain.NotifyChannelTypeDingTalk:
return pDingTalk.NewNotifier(&pDingTalk.NotifierConfig{
AccessToken: maps.GetValueAsString(channelConfig, "accessToken"),
Secret: maps.GetValueAsString(channelConfig, "secret"),
})
case domain.NotifyChannelTypeEmail:
return pEmail.NewNotifier(&pEmail.NotifierConfig{
SmtpHost: maps.GetValueAsString(channelConfig, "smtpHost"),
SmtpPort: maps.GetValueAsInt32(channelConfig, "smtpPort"),
SmtpTLS: maps.GetValueOrDefaultAsBool(channelConfig, "smtpTLS", true),
Username: maps.GetValueOrDefaultAsString(channelConfig, "username", maps.GetValueAsString(channelConfig, "senderAddress")),
Password: maps.GetValueAsString(channelConfig, "password"),
SenderAddress: maps.GetValueAsString(channelConfig, "senderAddress"),
ReceiverAddress: maps.GetValueAsString(channelConfig, "receiverAddress"),
})
case domain.NotifyChannelTypeLark:
return pLark.NewNotifier(&pLark.NotifierConfig{
WebhookUrl: maps.GetValueAsString(channelConfig, "webhookUrl"),
})
case domain.NotifyChannelTypeServerChan:
return pServerChan.NewNotifier(&pServerChan.NotifierConfig{
Url: maps.GetValueAsString(channelConfig, "url"),
})
case domain.NotifyChannelTypeTelegram:
return pTelegram.NewNotifier(&pTelegram.NotifierConfig{
ApiToken: maps.GetValueAsString(channelConfig, "apiToken"),
ChatId: maps.GetValueAsInt64(channelConfig, "chatId"),
})
case domain.NotifyChannelTypeWebhook:
return pWebhook.NewNotifier(&pWebhook.NotifierConfig{
Url: maps.GetValueAsString(channelConfig, "url"),
AllowInsecureConnections: maps.GetValueAsBool(channelConfig, "allowInsecureConnections"),
})
case domain.NotifyChannelTypeWeCom:
return pWeCom.NewNotifier(&pWeCom.NotifierConfig{
WebhookUrl: maps.GetValueAsString(channelConfig, "webhookUrl"),
})
}
return nil, fmt.Errorf("unsupported notifier channel: %s", channelConfig)
}

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/domain/dtos"
)
const (
@@ -12,35 +13,30 @@ const (
notifyTestBody = "欢迎使用 Certimate ,这是一条测试通知。"
)
type SettingRepository interface {
GetByName(ctx context.Context, name string) (*domain.Setting, error)
type settingsRepository interface {
GetByName(ctx context.Context, name string) (*domain.Settings, error)
}
type NotifyService struct {
settingRepo SettingRepository
settingsRepo settingsRepository
}
func NewNotifyService(settingRepo SettingRepository) *NotifyService {
func NewNotifyService(settingsRepo settingsRepository) *NotifyService {
return &NotifyService{
settingRepo: settingRepo,
settingsRepo: settingsRepo,
}
}
func (n *NotifyService) Test(ctx context.Context, req *domain.NotifyTestPushReq) error {
setting, err := n.settingRepo.GetByName(ctx, "notifyChannels")
func (n *NotifyService) Test(ctx context.Context, req *dtos.NotifyTestPushReq) error {
settings, err := n.settingsRepo.GetByName(ctx, "notifyChannels")
if err != nil {
return fmt.Errorf("get notify channels setting failed: %w", err)
return fmt.Errorf("failed to get notify channels settings: %w", err)
}
conf, err := setting.GetChannelContent(req.Channel)
channelConfig, err := settings.GetNotifyChannelConfig(string(req.Channel))
if err != nil {
return fmt.Errorf("get notify channel %s config failed: %w", req.Channel, err)
return fmt.Errorf("failed to get notify channel \"%s\" config: %w", req.Channel, err)
}
return SendTest(&sendTestParam{
Title: notifyTestTitle,
Content: notifyTestBody,
Channel: req.Channel,
Conf: conf,
})
return SendToChannel(notifyTestTitle, notifyTestBody, string(req.Channel), channelConfig)
}

View File

@@ -0,0 +1,40 @@
package acmehttpreq
import (
"net/url"
"time"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/providers/dns/httpreq"
)
type ChallengeProviderConfig struct {
Endpoint string `json:"endpoint"`
Mode string `json:"mode"`
Username string `json:"username"`
Password string `json:"password"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
}
func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
if config == nil {
panic("config is nil")
}
endpoint, _ := url.Parse(config.Endpoint)
providerConfig := httpreq.NewDefaultConfig()
providerConfig.Endpoint = endpoint
providerConfig.Mode = config.Mode
providerConfig.Username = config.Username
providerConfig.Password = config.Password
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
provider, err := httpreq.NewDNSProviderConfig(providerConfig)
if err != nil {
return nil, err
}
return provider, nil
}

View File

@@ -0,0 +1,38 @@
package aliyun
import (
"time"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/providers/dns/alidns"
)
type ChallengeProviderConfig struct {
AccessKeyId string `json:"accessKeyId"`
AccessKeySecret string `json:"accessKeySecret"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
if config == nil {
panic("config is nil")
}
providerConfig := alidns.NewDefaultConfig()
providerConfig.APIKey = config.AccessKeyId
providerConfig.SecretKey = config.AccessKeySecret
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = int(config.DnsTTL)
}
provider, err := alidns.NewDNSProviderConfig(providerConfig)
if err != nil {
return nil, err
}
return provider, nil
}

View File

@@ -0,0 +1,42 @@
package awsroute53
import (
"time"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/providers/dns/route53"
)
type ChallengeProviderConfig struct {
AccessKeyId string `json:"accessKeyId"`
SecretAccessKey string `json:"secretAccessKey"`
Region string `json:"region"`
HostedZoneId string `json:"hostedZoneId"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
if config == nil {
panic("config is nil")
}
providerConfig := route53.NewDefaultConfig()
providerConfig.AccessKeyID = config.AccessKeyId
providerConfig.SecretAccessKey = config.SecretAccessKey
providerConfig.Region = config.Region
providerConfig.HostedZoneID = config.HostedZoneId
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = int(config.DnsTTL)
}
provider, err := route53.NewDNSProviderConfig(providerConfig)
if err != nil {
return nil, err
}
return provider, nil
}

View File

@@ -0,0 +1,50 @@
package azuredns
import (
"time"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/providers/dns/azuredns"
azcommon "github.com/usual2970/certimate/internal/pkg/vendors/azure-sdk/common"
)
type ChallengeProviderConfig struct {
TenantId string `json:"tenantId"`
ClientId string `json:"clientId"`
ClientSecret string `json:"clientSecret"`
CloudName string `json:"cloudName,omitempty"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
if config == nil {
panic("config is nil")
}
providerConfig := azuredns.NewDefaultConfig()
providerConfig.TenantID = config.TenantId
providerConfig.ClientID = config.ClientId
providerConfig.ClientSecret = config.ClientSecret
if config.CloudName != "" {
env, err := azcommon.GetCloudEnvironmentConfiguration(config.CloudName)
if err != nil {
return nil, err
}
providerConfig.Environment = env
}
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = int(config.DnsTTL)
}
provider, err := azuredns.NewDNSProviderConfig(providerConfig)
if err != nil {
return nil, err
}
return provider, nil
}

View File

@@ -0,0 +1,39 @@
package baiducloud
import (
"time"
"github.com/go-acme/lego/v4/challenge"
internal "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/baiducloud/internal"
)
type ChallengeProviderConfig struct {
AccessKeyId string `json:"accessKeyId"`
SecretAccessKey string `json:"secretAccessKey"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
if config == nil {
panic("config is nil")
}
providerConfig := internal.NewDefaultConfig()
providerConfig.AccessKeyID = config.AccessKeyId
providerConfig.SecretAccessKey = config.SecretAccessKey
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = config.DnsTTL
}
provider, err := internal.NewDNSProviderConfig(providerConfig)
if err != nil {
return nil, err
}
return provider, nil
}

View File

@@ -0,0 +1,204 @@
package lego_baiducloud
import (
"errors"
"fmt"
"strings"
"time"
bceDns "github.com/baidubce/bce-sdk-go/services/dns"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/platform/config/env"
"github.com/google/uuid"
)
const (
envNamespace = "BAIDUCLOUD_"
EnvAccessKeyID = envNamespace + "ACCESS_KEY_ID"
EnvSecretAccessKey = envNamespace + "SECRET_ACCESS_KEY"
EnvTTL = envNamespace + "TTL"
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT"
)
var _ challenge.ProviderTimeout = (*DNSProvider)(nil)
type Config struct {
AccessKeyID string
SecretAccessKey string
PropagationTimeout time.Duration
PollingInterval time.Duration
TTL int32
HTTPTimeout time.Duration
}
type DNSProvider struct {
client *bceDns.Client
config *Config
}
func NewDefaultConfig() *Config {
return &Config{
TTL: int32(env.GetOrDefaultInt(EnvTTL, 300)),
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute),
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
}
}
func NewDNSProvider() (*DNSProvider, error) {
values, err := env.Get(EnvAccessKeyID, EnvSecretAccessKey)
if err != nil {
return nil, fmt.Errorf("baiducloud: %w", err)
}
config := NewDefaultConfig()
config.AccessKeyID = values[EnvAccessKeyID]
config.SecretAccessKey = values[EnvSecretAccessKey]
return NewDNSProviderConfig(config)
}
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
if config == nil {
return nil, errors.New("baiducloud: the configuration of the DNS provider is nil")
}
client, err := bceDns.NewClient(config.AccessKeyID, config.SecretAccessKey, "")
if err != nil {
return nil, err
} else {
if client.Config != nil {
client.Config.ConnectionTimeoutInMillis = int(config.HTTPTimeout.Milliseconds())
}
}
return &DNSProvider{
client: client,
config: config,
}, nil
}
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
info := dns01.GetChallengeInfo(domain, keyAuth)
authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
if err != nil {
return fmt.Errorf("baiducloud: %w", err)
}
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone)
if err != nil {
return fmt.Errorf("baiducloud: %w", err)
}
if err := d.addOrUpdateDNSRecord(dns01.UnFqdn(authZone), subDomain, info.Value); err != nil {
return fmt.Errorf("baiducloud: %w", err)
}
return nil
}
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
info := dns01.GetChallengeInfo(domain, keyAuth)
authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
if err != nil {
return fmt.Errorf("baiducloud: %w", err)
}
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone)
if err != nil {
return fmt.Errorf("baiducloud: %w", err)
}
if err := d.removeDNSRecord(dns01.UnFqdn(authZone), subDomain); err != nil {
return fmt.Errorf("baiducloud: %w", err)
}
return nil
}
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
return d.config.PropagationTimeout, d.config.PollingInterval
}
func (d *DNSProvider) getDNSRecord(zoneName, subDomain string) (*bceDns.Record, error) {
pageMarker := ""
pageSize := 1000
for {
request := &bceDns.ListRecordRequest{}
request.Rr = subDomain
request.Marker = pageMarker
request.MaxKeys = pageSize
response, err := d.client.ListRecord(zoneName, request)
if err != nil {
return nil, err
}
for _, record := range response.Records {
if record.Type == "TXT" && record.Rr == subDomain {
return &record, nil
}
}
if len(response.Records) < pageSize {
break
}
pageMarker = response.NextMarker
}
return nil, nil
}
func (d *DNSProvider) addOrUpdateDNSRecord(zoneName, subDomain, value string) error {
record, err := d.getDNSRecord(zoneName, subDomain)
if err != nil {
return err
}
if record == nil {
request := &bceDns.CreateRecordRequest{
Type: "TXT",
Rr: subDomain,
Value: value,
Ttl: &d.config.TTL,
}
err := d.client.CreateRecord(zoneName, request, d.generateClientToken())
return err
} else {
request := &bceDns.UpdateRecordRequest{
Type: "TXT",
Rr: subDomain,
Value: value,
Ttl: &d.config.TTL,
}
err := d.client.UpdateRecord(zoneName, record.Id, request, d.generateClientToken())
return err
}
}
func (d *DNSProvider) removeDNSRecord(zoneName, subDomain string) error {
record, err := d.getDNSRecord(zoneName, subDomain)
if err != nil {
return err
}
if record == nil {
return nil
} else {
err = d.client.DeleteRecord(zoneName, record.Id, d.generateClientToken())
return err
}
}
func (d *DNSProvider) generateClientToken() string {
return strings.ReplaceAll(uuid.New().String(), "-", "")
}

View File

@@ -0,0 +1,36 @@
package cloudflare
import (
"time"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/providers/dns/cloudflare"
)
type ChallengeProviderConfig struct {
DnsApiToken string `json:"dnsApiToken"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
if config == nil {
panic("config is nil")
}
providerConfig := cloudflare.NewDefaultConfig()
providerConfig.AuthToken = config.DnsApiToken
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = int(config.DnsTTL)
}
provider, err := cloudflare.NewDNSProviderConfig(providerConfig)
if err != nil {
return nil, err
}
return provider, nil
}

View File

@@ -0,0 +1,38 @@
package cloudns
import (
"time"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/providers/dns/cloudns"
)
type ChallengeProviderConfig struct {
AuthId string `json:"authId"`
AuthPassword string `json:"authPassword"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
if config == nil {
panic("config is nil")
}
providerConfig := cloudns.NewDefaultConfig()
providerConfig.AuthID = config.AuthId
providerConfig.AuthPassword = config.AuthPassword
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = int(config.DnsTTL)
}
provider, err := cloudns.NewDNSProviderConfig(providerConfig)
if err != nil {
return nil, err
}
return provider, nil
}

View File

@@ -0,0 +1,40 @@
package cmcccloud
import (
"errors"
"time"
"github.com/go-acme/lego/v4/challenge"
"github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/cmcccloud/internal"
)
type ChallengeProviderConfig struct {
AccessKeyId string `json:"accessKeyId"`
AccessKeySecret string `json:"accessKeySecret"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
if config == nil {
return nil, errors.New("config is nil")
}
providerConfig := internal.NewDefaultConfig()
providerConfig.AccessKey = config.AccessKeyId
providerConfig.SecretKey = config.AccessKeySecret
if config.DnsTTL != 0 {
providerConfig.TTL = config.DnsTTL
}
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
provider, err := internal.NewDNSProviderConfig(providerConfig)
if err != nil {
return nil, err
}
return provider, nil
}

View File

@@ -0,0 +1,221 @@
package internal
import (
"encoding/json"
"errors"
"fmt"
"strings"
"time"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/platform/config/env"
"gitlab.ecloud.com/ecloud/ecloudsdkclouddns"
"gitlab.ecloud.com/ecloud/ecloudsdkclouddns/model"
"gitlab.ecloud.com/ecloud/ecloudsdkcore/config"
)
const (
envNamespace = "CMCCCLOUD_"
EnvAccessKey = envNamespace + "ACCESS_KEY"
EnvSecretKey = envNamespace + "SECRET_KEY"
EnvTTL = envNamespace + "TTL"
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
EnvReadTimeOut = envNamespace + "READ_TIMEOUT"
EnvConnectTimeout = envNamespace + "CONNECT_TIMEOUT"
)
var _ challenge.ProviderTimeout = (*DNSProvider)(nil)
type Config struct {
AccessKey string
SecretKey string
ReadTimeOut int
ConnectTimeout int
PropagationTimeout time.Duration
PollingInterval time.Duration
TTL int32
}
type DNSProvider struct {
client *ecloudsdkclouddns.Client
config *Config
}
func NewDefaultConfig() *Config {
return &Config{
ReadTimeOut: env.GetOrDefaultInt(EnvReadTimeOut, 30),
ConnectTimeout: env.GetOrDefaultInt(EnvConnectTimeout, 30),
TTL: int32(env.GetOrDefaultInt(EnvTTL, 600)),
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute),
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
}
}
func NewDNSProvider() (*DNSProvider, error) {
values, err := env.Get(EnvAccessKey, EnvSecretKey)
if err != nil {
return nil, fmt.Errorf("cmccecloud: %w", err)
}
cfg := NewDefaultConfig()
cfg.AccessKey = values[EnvAccessKey]
cfg.SecretKey = values[EnvSecretKey]
return NewDNSProviderConfig(cfg)
}
func NewDNSProviderConfig(cfg *Config) (*DNSProvider, error) {
if cfg == nil {
return nil, errors.New("cmccecloud: the configuration of the DNS provider is nil")
}
client := ecloudsdkclouddns.NewClient(&config.Config{
AccessKey: cfg.AccessKey,
SecretKey: cfg.SecretKey,
// 资源池常量见: https://ecloud.10086.cn/op-help-center/doc/article/54462
// 默认全局
PoolId: "CIDC-CORE-00",
ReadTimeOut: cfg.ReadTimeOut,
ConnectTimeout: cfg.ConnectTimeout,
})
return &DNSProvider{
client: client,
config: cfg,
}, nil
}
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
info := dns01.GetChallengeInfo(domain, keyAuth)
zoneName, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
if err != nil {
return fmt.Errorf("cmccecloud: %w", err)
}
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zoneName)
if err != nil {
return fmt.Errorf("cmccecloud: %w", err)
}
readDomain := strings.Trim(zoneName, ".")
record, err := d.getDomainRecord(readDomain, subDomain)
if err != nil {
return err
}
if record == nil {
// add new record
resp, err := d.client.CreateRecordOpenapi(&model.CreateRecordOpenapiRequest{
CreateRecordOpenapiBody: &model.CreateRecordOpenapiBody{
LineId: "0", // 默认线路
Rr: subDomain,
DomainName: readDomain,
Description: "from certimate",
Type: model.CreateRecordOpenapiBodyTypeEnumTxt,
Value: info.Value,
Ttl: &d.config.TTL,
},
})
if err != nil {
return fmt.Errorf("lego: %w", err)
}
if resp.State != model.CreateRecordOpenapiResponseStateEnumOk {
return fmt.Errorf("lego: create record failed, response state: %s, message: %s, code: %s", resp.State, resp.ErrorMessage, resp.ErrorCode)
}
return nil
} else {
// update record
resp, err := d.client.ModifyRecordOpenapi(&model.ModifyRecordOpenapiRequest{
ModifyRecordOpenapiBody: &model.ModifyRecordOpenapiBody{
RecordId: record.RecordId,
Rr: subDomain,
DomainName: readDomain,
Description: "from certmate",
LineId: "0",
Type: model.ModifyRecordOpenapiBodyTypeEnumTxt,
Value: info.Value,
Ttl: &d.config.TTL,
},
})
if err != nil {
return fmt.Errorf("lego: %w", err)
}
if resp.State != model.ModifyRecordOpenapiResponseStateEnumOk {
return fmt.Errorf("lego: create record failed, response state: %s", resp.State)
}
return nil
}
}
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
challengeInfo := dns01.GetChallengeInfo(domain, keyAuth)
zoneName, err := dns01.FindZoneByFqdn(challengeInfo.FQDN)
if err != nil {
return fmt.Errorf("cmccecloud: %w", err)
}
subDomain, err := dns01.ExtractSubDomain(challengeInfo.FQDN, zoneName)
if err != nil {
return fmt.Errorf("cmccecloud: %w", err)
}
readDomain := strings.Trim(zoneName, ".")
record, err := d.getDomainRecord(readDomain, subDomain)
if err != nil {
return err
}
if record == nil {
return nil
}
resp, err := d.client.DeleteRecordOpenapi(&model.DeleteRecordOpenapiRequest{
DeleteRecordOpenapiBody: &model.DeleteRecordOpenapiBody{
RecordIdList: []string{record.RecordId},
},
})
if err != nil {
return fmt.Errorf("lego: %w", err)
}
if resp.State != model.DeleteRecordOpenapiResponseStateEnumOk {
return fmt.Errorf("lego: delete record failed, response state: %s", resp.State)
}
return nil
}
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
return d.config.PropagationTimeout, d.config.PollingInterval
}
func (d *DNSProvider) getDomainRecord(domain string, rr string) (*model.ListRecordOpenapiResponseData, error) {
pageSize := int32(50)
page := int32(1)
for {
resp, err := d.client.ListRecordOpenapi(&model.ListRecordOpenapiRequest{
ListRecordOpenapiBody: &model.ListRecordOpenapiBody{
DomainName: domain,
},
ListRecordOpenapiQuery: &model.ListRecordOpenapiQuery{
PageSize: &pageSize,
Page: &page,
},
})
if err != nil {
return nil, err
}
if resp.State != model.ListRecordOpenapiResponseStateEnumOk {
respStr, _ := json.Marshal(resp)
return nil, fmt.Errorf("request error. %s", string(respStr))
}
if resp.Body.Data != nil {
for _, item := range *resp.Body.Data {
if item.Rr == rr {
return &item, nil
}
}
}
if resp.Body.TotalPages == nil || page >= *resp.Body.TotalPages {
return nil, nil
}
page++
}
}

View File

@@ -0,0 +1,39 @@
package dnsla
import (
"time"
"github.com/go-acme/lego/v4/challenge"
internal "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/dnsla/internal"
)
type ChallengeProviderConfig struct {
ApiId string `json:"apiId"`
ApiSecret string `json:"apiSecret"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
if config == nil {
panic("config is nil")
}
providerConfig := internal.NewDefaultConfig()
providerConfig.APIId = config.ApiId
providerConfig.APISecret = config.ApiSecret
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = int(config.DnsTTL)
}
provider, err := internal.NewDNSProviderConfig(providerConfig)
if err != nil {
return nil, err
}
return provider, nil
}

View File

@@ -0,0 +1,240 @@
package lego_dnsla
import (
"errors"
"fmt"
"strings"
"time"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/platform/config/env"
dnslasdk "github.com/usual2970/certimate/internal/pkg/vendors/dnsla-sdk"
)
const (
envNamespace = "DNSLA_"
EnvAPIId = envNamespace + "API_ID"
EnvAPISecret = envNamespace + "API_KEY"
EnvTTL = envNamespace + "TTL"
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT"
)
var _ challenge.ProviderTimeout = (*DNSProvider)(nil)
type Config struct {
APIId string
APISecret string
PropagationTimeout time.Duration
PollingInterval time.Duration
TTL int
HTTPTimeout time.Duration
}
type DNSProvider struct {
client *dnslasdk.Client
config *Config
}
func NewDefaultConfig() *Config {
return &Config{
TTL: env.GetOrDefaultInt(EnvTTL, 300),
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute),
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
}
}
func NewDNSProvider() (*DNSProvider, error) {
values, err := env.Get(EnvAPIId, EnvAPISecret)
if err != nil {
return nil, fmt.Errorf("dnsla: %w", err)
}
config := NewDefaultConfig()
config.APIId = values[EnvAPIId]
config.APISecret = values[EnvAPISecret]
return NewDNSProviderConfig(config)
}
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
if config == nil {
return nil, errors.New("dnsla: the configuration of the DNS provider is nil")
}
client := dnslasdk.NewClient(config.APIId, config.APISecret).
WithTimeout(config.HTTPTimeout)
return &DNSProvider{
client: client,
config: config,
}, nil
}
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
info := dns01.GetChallengeInfo(domain, keyAuth)
authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
if err != nil {
return fmt.Errorf("dnsla: %w", err)
}
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone)
if err != nil {
return fmt.Errorf("dnsla: %w", err)
}
if err := d.addOrUpdateDNSRecord(dns01.UnFqdn(authZone), subDomain, info.Value); err != nil {
return fmt.Errorf("dnsla: %w", err)
}
return nil
}
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
info := dns01.GetChallengeInfo(domain, keyAuth)
authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
if err != nil {
return fmt.Errorf("dnsla: %w", err)
}
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone)
if err != nil {
return fmt.Errorf("dnsla: %w", err)
}
if err := d.removeDNSRecord(dns01.UnFqdn(authZone), subDomain); err != nil {
return fmt.Errorf("dnsla: %w", err)
}
return nil
}
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
return d.config.PropagationTimeout, d.config.PollingInterval
}
func (d *DNSProvider) getDNSZone(zoneName string) (*dnslasdk.DomainInfo, error) {
pageIndex := 1
pageSize := 100
for {
request := &dnslasdk.ListDomainsRequest{
PageIndex: int32(pageIndex),
PageSize: int32(pageSize),
}
response, err := d.client.ListDomains(request)
if err != nil {
return nil, err
}
if response.Data != nil {
for _, item := range response.Data.Results {
if strings.TrimRight(item.Domain, ".") == zoneName || strings.TrimRight(item.DisplayDomain, ".") == zoneName {
return item, nil
}
}
}
if response.Data == nil || len(response.Data.Results) < pageSize {
break
}
pageIndex++
}
return nil, fmt.Errorf("dnsla: zone %s not found", zoneName)
}
func (d *DNSProvider) getDNSZoneAndRecord(zoneName, subDomain string) (*dnslasdk.DomainInfo, *dnslasdk.RecordInfo, error) {
zone, err := d.getDNSZone(zoneName)
if err != nil {
return nil, nil, err
}
pageIndex := 1
pageSize := 100
for {
request := &dnslasdk.ListRecordsRequest{
DomainId: zone.Id,
Host: &subDomain,
PageIndex: int32(pageIndex),
PageSize: int32(pageSize),
}
response, err := d.client.ListRecords(request)
if err != nil {
return zone, nil, err
}
if response.Data != nil {
for _, record := range response.Data.Results {
if record.Type == 16 && (record.Host == subDomain || record.DisplayHost == subDomain) {
return zone, record, nil
}
}
}
if response.Data == nil || len(response.Data.Results) < pageSize {
break
}
pageIndex++
}
return zone, nil, nil
}
func (d *DNSProvider) addOrUpdateDNSRecord(zoneName, subDomain, value string) error {
zone, record, err := d.getDNSZoneAndRecord(zoneName, subDomain)
if err != nil {
return err
}
if record == nil {
request := &dnslasdk.CreateRecordRequest{
DomainId: zone.Id,
Type: 16,
Host: subDomain,
Data: value,
Ttl: int32(d.config.TTL),
}
_, err := d.client.CreateRecord(request)
return err
} else {
reqType := int32(16)
reqTtl := int32(d.config.TTL)
request := &dnslasdk.UpdateRecordRequest{
Id: record.Id,
Type: &reqType,
Host: &subDomain,
Data: &value,
Ttl: &reqTtl,
}
_, err := d.client.UpdateRecord(request)
return err
}
}
func (d *DNSProvider) removeDNSRecord(zoneName, subDomain string) error {
_, record, err := d.getDNSZoneAndRecord(zoneName, subDomain)
if err != nil {
return err
}
if record == nil {
return nil
} else {
request := &dnslasdk.DeleteRecordRequest{
Id: record.Id,
}
_, err = d.client.DeleteRecord(request)
return err
}
}

View File

@@ -0,0 +1,36 @@
package gcore
import (
"time"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/providers/dns/gcore"
)
type ChallengeProviderConfig struct {
ApiToken string `json:"apiToken"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
if config == nil {
panic("config is nil")
}
providerConfig := gcore.NewDefaultConfig()
providerConfig.APIToken = config.ApiToken
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = int(config.DnsTTL)
}
provider, err := gcore.NewDNSProviderConfig(providerConfig)
if err != nil {
return nil, err
}
return provider, nil
}

View File

@@ -0,0 +1,39 @@
package gname
import (
"time"
"github.com/go-acme/lego/v4/challenge"
internal "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname/internal"
)
type ChallengeProviderConfig struct {
AppId string `json:"appId"`
AppKey string `json:"appKey"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
if config == nil {
panic("config is nil")
}
providerConfig := internal.NewDefaultConfig()
providerConfig.AppID = config.AppId
providerConfig.AppKey = config.AppKey
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = int(config.DnsTTL)
}
provider, err := internal.NewDNSProviderConfig(providerConfig)
if err != nil {
return nil, err
}
return provider, nil
}

View File

@@ -0,0 +1,203 @@
package lego_gname
import (
"errors"
"fmt"
"time"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/platform/config/env"
gnamesdk "github.com/usual2970/certimate/internal/pkg/vendors/gname-sdk"
)
const (
envNamespace = "GNAME_"
EnvAppID = envNamespace + "APP_ID"
EnvAppKey = envNamespace + "APP_KEY"
EnvTTL = envNamespace + "TTL"
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT"
)
var _ challenge.ProviderTimeout = (*DNSProvider)(nil)
type Config struct {
AppID string
AppKey string
PropagationTimeout time.Duration
PollingInterval time.Duration
TTL int
HTTPTimeout time.Duration
}
type DNSProvider struct {
client *gnamesdk.Client
config *Config
}
func NewDefaultConfig() *Config {
return &Config{
TTL: env.GetOrDefaultInt(EnvTTL, 300),
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute),
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
}
}
func NewDNSProvider() (*DNSProvider, error) {
values, err := env.Get(EnvAppID, EnvAppKey)
if err != nil {
return nil, fmt.Errorf("gname: %w", err)
}
config := NewDefaultConfig()
config.AppID = values[EnvAppID]
config.AppKey = values[EnvAppKey]
return NewDNSProviderConfig(config)
}
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
if config == nil {
return nil, errors.New("gname: the configuration of the DNS provider is nil")
}
client := gnamesdk.NewClient(config.AppID, config.AppKey).
WithTimeout(config.HTTPTimeout)
return &DNSProvider{
client: client,
config: config,
}, nil
}
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
info := dns01.GetChallengeInfo(domain, keyAuth)
authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
if err != nil {
return fmt.Errorf("gname: %w", err)
}
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone)
if err != nil {
return fmt.Errorf("gname: %w", err)
}
if err := d.addOrUpdateDNSRecord(dns01.UnFqdn(authZone), subDomain, info.Value); err != nil {
return fmt.Errorf("gname: %w", err)
}
return nil
}
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
info := dns01.GetChallengeInfo(domain, keyAuth)
authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
if err != nil {
return fmt.Errorf("gname: %w", err)
}
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone)
if err != nil {
return fmt.Errorf("gname: %w", err)
}
if err := d.removeDNSRecord(dns01.UnFqdn(authZone), subDomain); err != nil {
return fmt.Errorf("gname: %w", err)
}
return nil
}
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
return d.config.PropagationTimeout, d.config.PollingInterval
}
func (d *DNSProvider) getDNSRecord(zoneName, subDomain string) (*gnamesdk.ResolutionRecord, error) {
page := 1
pageSize := 20
for {
request := &gnamesdk.ListDomainResolutionRequest{}
request.ZoneName = zoneName
request.Page = &page
request.PageSize = &pageSize
response, err := d.client.ListDomainResolution(request)
if err != nil {
return nil, err
}
for _, record := range response.Data {
if record.RecordType == "TXT" && record.RecordName == subDomain {
return record, nil
}
}
if len(response.Data) == 0 {
break
}
if response.Page*response.PageSize >= response.Count {
break
}
page++
}
return nil, nil
}
func (d *DNSProvider) addOrUpdateDNSRecord(zoneName, subDomain, value string) error {
record, err := d.getDNSRecord(zoneName, subDomain)
if err != nil {
return err
}
if record == nil {
request := &gnamesdk.AddDomainResolutionRequest{
ZoneName: zoneName,
RecordType: "TXT",
RecordName: subDomain,
RecordValue: value,
TTL: d.config.TTL,
}
_, err := d.client.AddDomainResolution(request)
return err
} else {
request := &gnamesdk.ModifyDomainResolutionRequest{
ID: record.ID,
ZoneName: zoneName,
RecordType: "TXT",
RecordName: subDomain,
RecordValue: value,
TTL: d.config.TTL,
}
_, err := d.client.ModifyDomainResolution(request)
return err
}
}
func (d *DNSProvider) removeDNSRecord(zoneName, subDomain string) error {
record, err := d.getDNSRecord(zoneName, subDomain)
if err != nil {
return err
}
if record == nil {
return nil
}
request := &gnamesdk.DeleteDomainResolutionRequest{
ZoneName: zoneName,
RecordID: record.ID,
}
_, err = d.client.DeleteDomainResolution(request)
return err
}

View File

@@ -0,0 +1,38 @@
package godaddy
import (
"time"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/providers/dns/godaddy"
)
type ChallengeProviderConfig struct {
ApiKey string `json:"apiKey"`
ApiSecret string `json:"apiSecret"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
if config == nil {
panic("config is nil")
}
providerConfig := godaddy.NewDefaultConfig()
providerConfig.APIKey = config.ApiKey
providerConfig.APISecret = config.ApiSecret
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = int(config.DnsTTL)
}
provider, err := godaddy.NewDNSProviderConfig(providerConfig)
if err != nil {
return nil, err
}
return provider, nil
}

View File

@@ -0,0 +1,46 @@
package huaweicloud
import (
"time"
"github.com/go-acme/lego/v4/challenge"
hwc "github.com/go-acme/lego/v4/providers/dns/huaweicloud"
)
type ChallengeProviderConfig struct {
AccessKeyId string `json:"accessKeyId"`
SecretAccessKey string `json:"secretAccessKey"`
Region string `json:"region"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
if config == nil {
panic("config is nil")
}
region := config.Region
if region == "" {
// 华为云的 SDK 要求必须传一个区域,实际上 DNS-01 流程里用不到,但不传会报错
region = "cn-north-1"
}
providerConfig := hwc.NewDefaultConfig()
providerConfig.AccessKeyID = config.AccessKeyId
providerConfig.SecretAccessKey = config.SecretAccessKey
providerConfig.Region = region
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = config.DnsTTL
}
provider, err := hwc.NewDNSProviderConfig(providerConfig)
if err != nil {
return nil, err
}
return provider, nil
}

View File

@@ -0,0 +1,238 @@
package lego_jdcloud
import (
"errors"
"fmt"
"time"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/platform/config/env"
jdCore "github.com/jdcloud-api/jdcloud-sdk-go/core"
jdDnsApi "github.com/jdcloud-api/jdcloud-sdk-go/services/domainservice/apis"
jdDnsClient "github.com/jdcloud-api/jdcloud-sdk-go/services/domainservice/client"
jdDnsModel "github.com/jdcloud-api/jdcloud-sdk-go/services/domainservice/models"
)
const (
envNamespace = "JDCLOUD_"
EnvAccessKeyID = envNamespace + "ACCESS_KEY_ID"
EnvAccessKeySecret = envNamespace + "ACCESS_KEY_SECRET"
EnvRegionId = envNamespace + "REGION_ID"
EnvTTL = envNamespace + "TTL"
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT"
)
var _ challenge.ProviderTimeout = (*DNSProvider)(nil)
type Config struct {
AccessKeyID string
AccessKeySecret string
RegionId string
PropagationTimeout time.Duration
PollingInterval time.Duration
TTL int32
HTTPTimeout time.Duration
}
type DNSProvider struct {
client *jdDnsClient.DomainserviceClient
config *Config
}
func NewDefaultConfig() *Config {
return &Config{
TTL: int32(env.GetOrDefaultInt(EnvTTL, 300)),
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute),
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
}
}
func NewDNSProvider() (*DNSProvider, error) {
values, err := env.Get(EnvAccessKeyID, EnvAccessKeySecret)
if err != nil {
return nil, fmt.Errorf("jdcloud: %w", err)
}
config := NewDefaultConfig()
config.AccessKeyID = values[EnvAccessKeyID]
config.AccessKeySecret = values[EnvAccessKeySecret]
config.RegionId = values[EnvRegionId]
return NewDNSProviderConfig(config)
}
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
if config == nil {
return nil, errors.New("jdcloud: the configuration of the DNS provider is nil")
}
clientCredentials := jdCore.NewCredentials(config.AccessKeyID, config.AccessKeySecret)
client := jdDnsClient.NewDomainserviceClient(clientCredentials)
clientConfig := &client.Config
clientConfig.SetTimeout(config.HTTPTimeout)
client.SetConfig(clientConfig)
client.SetLogger(jdCore.NewDefaultLogger(jdCore.LogWarn))
return &DNSProvider{
client: client,
config: config,
}, nil
}
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
info := dns01.GetChallengeInfo(domain, keyAuth)
authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
if err != nil {
return fmt.Errorf("jdcloud: %w", err)
}
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone)
if err != nil {
return fmt.Errorf("jdcloud: %w", err)
}
if err := d.addOrUpdateDNSRecord(dns01.UnFqdn(authZone), subDomain, info.Value); err != nil {
return fmt.Errorf("jdcloud: %w", err)
}
return nil
}
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
info := dns01.GetChallengeInfo(domain, keyAuth)
authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
if err != nil {
return fmt.Errorf("jdcloud: %w", err)
}
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone)
if err != nil {
return fmt.Errorf("jdcloud: %w", err)
}
if err := d.removeDNSRecord(dns01.UnFqdn(authZone), subDomain); err != nil {
return fmt.Errorf("jdcloud: %w", err)
}
return nil
}
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
return d.config.PropagationTimeout, d.config.PollingInterval
}
func (d *DNSProvider) getDNSZone(zoneName string) (*jdDnsModel.DomainInfo, error) {
pageNumber := 1
pageSize := 10
for {
request := jdDnsApi.NewDescribeDomainsRequest(d.config.RegionId, pageNumber, pageSize)
request.SetDomainName(zoneName)
response, err := d.client.DescribeDomains(request)
if err != nil {
return nil, err
}
for _, item := range response.Result.DataList {
if item.DomainName == zoneName {
return &item, nil
}
}
if len(response.Result.DataList) < pageSize {
break
}
pageNumber++
}
return nil, fmt.Errorf("jdcloud: zone %s not found", zoneName)
}
func (d *DNSProvider) getDNSZoneAndRecord(zoneName, subDomain string) (*jdDnsModel.DomainInfo, *jdDnsModel.RRInfo, error) {
zone, err := d.getDNSZone(zoneName)
if err != nil {
return nil, nil, err
}
pageNumber := 1
pageSize := 10
for {
request := jdDnsApi.NewDescribeResourceRecordRequest(d.config.RegionId, fmt.Sprintf("%d", zone.Id))
request.SetSearch(subDomain)
request.SetPageNumber(pageNumber)
request.SetPageSize(pageSize)
response, err := d.client.DescribeResourceRecord(request)
if err != nil {
return zone, nil, err
}
for _, record := range response.Result.DataList {
if record.Type == "TXT" && record.HostRecord == subDomain {
return zone, &record, nil
}
}
if len(response.Result.DataList) < pageSize {
break
}
pageNumber++
}
return zone, nil, nil
}
func (d *DNSProvider) addOrUpdateDNSRecord(zoneName, subDomain, value string) error {
zone, record, err := d.getDNSZoneAndRecord(zoneName, subDomain)
if err != nil {
return err
}
if record == nil {
request := jdDnsApi.NewCreateResourceRecordRequest(d.config.RegionId, fmt.Sprintf("%d", zone.Id), &jdDnsModel.AddRR{
Type: "TXT",
HostRecord: subDomain,
HostValue: value,
Ttl: int(d.config.TTL),
ViewValue: -1,
})
_, err := d.client.CreateResourceRecord(request)
return err
} else {
request := jdDnsApi.NewModifyResourceRecordRequest(d.config.RegionId, fmt.Sprintf("%d", zone.Id), fmt.Sprintf("%d", record.Id), &jdDnsModel.UpdateRR{
Type: "TXT",
HostRecord: subDomain,
HostValue: value,
Ttl: int(d.config.TTL),
ViewValue: -1,
})
_, err := d.client.ModifyResourceRecord(request)
return err
}
}
func (d *DNSProvider) removeDNSRecord(zoneName, subDomain string) error {
zone, record, err := d.getDNSZoneAndRecord(zoneName, subDomain)
if err != nil {
return err
}
if record == nil {
return nil
} else {
request := jdDnsApi.NewDeleteResourceRecordRequest(d.config.RegionId, fmt.Sprintf("%d", zone.Id), fmt.Sprintf("%d", record.Id))
_, err = d.client.DeleteResourceRecord(request)
return err
}
}

View File

@@ -0,0 +1,47 @@
package jdcloud
import (
"time"
"github.com/go-acme/lego/v4/challenge"
internal "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/jdcloud/internal"
)
type ChallengeProviderConfig struct {
AccessKeyId string `json:"accessKeyId"`
AccessKeySecret string `json:"accessKeySecret"`
RegionId string `json:"regionId"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
if config == nil {
panic("config is nil")
}
regionId := config.RegionId
if regionId == "" {
// 京东云的 SDK 要求必须传一个区域,实际上 DNS-01 流程里用不到,但不传会报错
regionId = "cn-north-1"
}
providerConfig := internal.NewDefaultConfig()
providerConfig.AccessKeyID = config.AccessKeyId
providerConfig.AccessKeySecret = config.AccessKeySecret
providerConfig.RegionId = regionId
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = config.DnsTTL
}
provider, err := internal.NewDNSProviderConfig(providerConfig)
if err != nil {
return nil, err
}
return provider, nil
}

View File

@@ -0,0 +1,38 @@
package namedotcom
import (
"time"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/providers/dns/namecheap"
)
type ChallengeProviderConfig struct {
Username string `json:"username"`
ApiKey string `json:"apiKey"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
if config == nil {
panic("config is nil")
}
providerConfig := namecheap.NewDefaultConfig()
providerConfig.APIUser = config.Username
providerConfig.APIKey = config.ApiKey
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = int(config.DnsTTL)
}
provider, err := namecheap.NewDNSProviderConfig(providerConfig)
if err != nil {
return nil, err
}
return provider, nil
}

View File

@@ -0,0 +1,38 @@
package namedotcom
import (
"time"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/providers/dns/namedotcom"
)
type ChallengeProviderConfig struct {
Username string `json:"username"`
ApiToken string `json:"apiToken"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
if config == nil {
panic("config is nil")
}
providerConfig := namedotcom.NewDefaultConfig()
providerConfig.Username = config.Username
providerConfig.APIToken = config.ApiToken
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = int(config.DnsTTL)
}
provider, err := namedotcom.NewDNSProviderConfig(providerConfig)
if err != nil {
return nil, err
}
return provider, nil
}

View File

@@ -0,0 +1,36 @@
package namesilo
import (
"time"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/providers/dns/namesilo"
)
type ChallengeProviderConfig struct {
ApiKey string `json:"apiKey"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
if config == nil {
panic("config is nil")
}
providerConfig := namesilo.NewDefaultConfig()
providerConfig.APIKey = config.ApiKey
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = int(config.DnsTTL)
}
provider, err := namesilo.NewDNSProviderConfig(providerConfig)
if err != nil {
return nil, err
}
return provider, nil
}

View File

@@ -0,0 +1,36 @@
package ns1
import (
"time"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/providers/dns/ns1"
)
type ChallengeProviderConfig struct {
ApiKey string `json:"apiKey"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
if config == nil {
panic("config is nil")
}
providerConfig := ns1.NewDefaultConfig()
providerConfig.APIKey = config.ApiKey
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = int(config.DnsTTL)
}
provider, err := ns1.NewDNSProviderConfig(providerConfig)
if err != nil {
return nil, err
}
return provider, nil
}

View File

@@ -0,0 +1,40 @@
package namesilo
import (
"net/url"
"time"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/providers/dns/pdns"
)
type ChallengeProviderConfig struct {
ApiUrl string `json:"apiUrl"`
ApiKey string `json:"apiKey"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
if config == nil {
panic("config is nil")
}
host, _ := url.Parse(config.ApiUrl)
providerConfig := pdns.NewDefaultConfig()
providerConfig.Host = host
providerConfig.APIKey = config.ApiKey
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = int(config.DnsTTL)
}
provider, err := pdns.NewDNSProviderConfig(providerConfig)
if err != nil {
return nil, err
}
return provider, nil
}

View File

@@ -0,0 +1,36 @@
package rainyun
import (
"time"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/providers/dns/rainyun"
)
type ChallengeProviderConfig struct {
ApiKey string `json:"apiKey"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
if config == nil {
panic("config is nil")
}
providerConfig := rainyun.NewDefaultConfig()
providerConfig.APIKey = config.ApiKey
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = int(config.DnsTTL)
}
provider, err := rainyun.NewDNSProviderConfig(providerConfig)
if err != nil {
return nil, err
}
return provider, nil
}

View File

@@ -0,0 +1,38 @@
package tencentcloud
import (
"time"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/providers/dns/tencentcloud"
)
type ChallengeProviderConfig struct {
SecretId string `json:"secretId"`
SecretKey string `json:"secretKey"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
if config == nil {
panic("config is nil")
}
providerConfig := tencentcloud.NewDefaultConfig()
providerConfig.SecretID = config.SecretId
providerConfig.SecretKey = config.SecretKey
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = int(config.DnsTTL)
}
provider, err := tencentcloud.NewDNSProviderConfig(providerConfig)
if err != nil {
return nil, err
}
return provider, nil
}

View File

@@ -0,0 +1,38 @@
package volcengine
import (
"time"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/providers/dns/volcengine"
)
type ChallengeProviderConfig struct {
AccessKeyId string `json:"accessKeyId"`
SecretAccessKey string `json:"secretAccessKey"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
if config == nil {
panic("config is nil")
}
providerConfig := volcengine.NewDefaultConfig()
providerConfig.AccessKey = config.AccessKeyId
providerConfig.SecretKey = config.SecretAccessKey
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = int(config.DnsTTL)
}
provider, err := volcengine.NewDNSProviderConfig(providerConfig)
if err != nil {
return nil, err
}
return provider, nil
}

View File

@@ -0,0 +1,38 @@
package westcn
import (
"time"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/providers/dns/westcn"
)
type ChallengeProviderConfig struct {
Username string `json:"username"`
ApiPassword string `json:"apiPassword"`
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
DnsTTL int32 `json:"dnsTTL,omitempty"`
}
func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
if config == nil {
panic("config is nil")
}
providerConfig := westcn.NewDefaultConfig()
providerConfig.Username = config.Username
providerConfig.Password = config.ApiPassword
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = int(config.DnsTTL)
}
provider, err := westcn.NewDNSProviderConfig(providerConfig)
if err != nil {
return nil, err
}
return provider, nil
}

Some files were not shown because too many files have changed in this diff Show More