feat(system-security): Support Hot Reloading of System Certificates (#7152)

Refs https://github.com/1Panel-dev/1Panel/issues/7129
pull/7155/head
zhengkunwang 2 days ago committed by GitHub
parent 7fdb0a5078
commit c3565f72c4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -381,6 +381,9 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) (*model.Website
logger.Println(i18n.GetMsgByKey("ExecShellSuccess")) logger.Println(i18n.GetMsgByKey("ExecShellSuccess"))
} }
} }
reloadSystemSSL(websiteSSL, logger)
return websiteSSL, nil return websiteSSL, nil
} }

@ -3,6 +3,7 @@ package service
import ( import (
"context" "context"
"crypto" "crypto"
"crypto/tls"
"crypto/x509" "crypto/x509"
"encoding/pem" "encoding/pem"
"fmt" "fmt"
@ -188,6 +189,31 @@ func printSSLLog(logger *log.Logger, msgKey string, params map[string]interface{
logger.Println(i18n.GetMsgWithMap(msgKey, params)) logger.Println(i18n.GetMsgWithMap(msgKey, params))
} }
func reloadSystemSSL(websiteSSL *model.WebsiteSSL, logger *log.Logger) {
systemSSLEnable, sslID := GetSystemSSL()
if systemSSLEnable && sslID == websiteSSL.ID {
fileOp := files.NewFileOp()
certPath := path.Join(global.CONF.System.BaseDir, "1panel/secret/server.crt")
keyPath := path.Join(global.CONF.System.BaseDir, "1panel/secret/server.key")
printSSLLog(logger, "StartUpdateSystemSSL", nil, logger == nil)
if err := fileOp.WriteFile(certPath, strings.NewReader(websiteSSL.Pem), 0600); err != nil {
logger.Printf("Failed to update the SSL certificate File for 1Panel System domain [%s] , err:%s", websiteSSL.PrimaryDomain, err.Error())
return
}
if err := fileOp.WriteFile(keyPath, strings.NewReader(websiteSSL.PrivateKey), 0600); err != nil {
logger.Printf("Failed to update the SSL certificate for 1Panel System domain [%s] , err:%s", websiteSSL.PrimaryDomain, err.Error())
return
}
newCert, err := tls.X509KeyPair([]byte(websiteSSL.Pem), []byte(websiteSSL.PrivateKey))
if err != nil {
logger.Printf("Failed to update the SSL certificate for 1Panel System domain [%s] , err:%s", websiteSSL.PrimaryDomain, err.Error())
return
}
printSSLLog(logger, "UpdateSystemSSLSuccess", nil, logger == nil)
constant.CertStore.Store(&newCert)
}
}
func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error { func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
var ( var (
err error err error
@ -344,6 +370,8 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
} }
printSSLLog(logger, "ApplyWebSiteSSLSuccess", nil, apply.DisableLog) printSSLLog(logger, "ApplyWebSiteSSLSuccess", nil, apply.DisableLog)
} }
reloadSystemSSL(websiteSSL, logger)
}() }()
return nil return nil

@ -1002,23 +1002,22 @@ func saveCertificateFile(websiteSSL *model.WebsiteSSL, logger *log.Logger) {
} }
} }
func GetSystemSSL() (bool, bool, uint) { func GetSystemSSL() (bool, uint) {
sslSetting, err := settingRepo.Get(settingRepo.WithByKey("SSL")) sslSetting, err := settingRepo.Get(settingRepo.WithByKey("SSL"))
if err != nil { if err != nil {
global.LOG.Errorf("load service ssl from setting failed, err: %v", err) global.LOG.Errorf("load service ssl from setting failed, err: %v", err)
return false, false, 0 return false, 0
} }
if sslSetting.Value == "enable" { if sslSetting.Value == "enable" {
sslID, _ := settingRepo.Get(settingRepo.WithByKey("SSLID")) sslID, _ := settingRepo.Get(settingRepo.WithByKey("SSLID"))
idValue, _ := strconv.Atoi(sslID.Value) idValue, _ := strconv.Atoi(sslID.Value)
if idValue <= 0 { if idValue <= 0 {
return false, false, 0 return false, 0
} }
auto, _ := settingRepo.Get(settingRepo.WithByKey("AutoRestart")) return true, uint(idValue)
return true, auto.Value == "enable", uint(idValue)
} }
return false, false, 0 return false, 0
} }
func UpdateSSLConfig(websiteSSL model.WebsiteSSL) error { func UpdateSSLConfig(websiteSSL model.WebsiteSSL) error {
@ -1037,22 +1036,7 @@ func UpdateSSLConfig(websiteSSL model.WebsiteSSL) error {
return buserr.WithErr(constant.ErrSSLApply, err) return buserr.WithErr(constant.ErrSSLApply, err)
} }
} }
enable, auto, sslID := GetSystemSSL() reloadSystemSSL(&websiteSSL, nil)
if enable && sslID == websiteSSL.ID {
fileOp := files.NewFileOp()
secretDir := path.Join(global.CONF.System.BaseDir, "1panel/secret")
if err := fileOp.WriteFile(path.Join(secretDir, "server.crt"), strings.NewReader(websiteSSL.Pem), 0600); err != nil {
global.LOG.Errorf("Failed to update the SSL certificate File for 1Panel System domain [%s] , err:%s", websiteSSL.PrimaryDomain, err.Error())
return err
}
if err := fileOp.WriteFile(path.Join(secretDir, "server.key"), strings.NewReader(websiteSSL.PrivateKey), 0600); err != nil {
global.LOG.Errorf("Failed to update the SSL certificate for 1Panel System domain [%s] , err:%s", websiteSSL.PrimaryDomain, err.Error())
return err
}
if auto {
_, _ = cmd.Exec("systemctl restart 1panel.service")
}
}
return nil return nil
} }

@ -1,5 +1,7 @@
package constant package constant
import "sync/atomic"
type DBContext string type DBContext string
const ( const (
@ -123,3 +125,5 @@ var DynamicRoutes = []string{
`^/databases/postgresql/setting/[^/]+/[^/]+$`, `^/databases/postgresql/setting/[^/]+/[^/]+$`,
`^/websites/[^/]+/config/[^/]+$`, `^/websites/[^/]+/config/[^/]+$`,
} }
var CertStore atomic.Value

@ -1,8 +1,6 @@
package job package job
import ( import (
"path"
"strings"
"time" "time"
"github.com/1Panel-dev/1Panel/backend/app/dto/request" "github.com/1Panel-dev/1Panel/backend/app/dto/request"
@ -10,9 +8,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/service" "github.com/1Panel-dev/1Panel/backend/app/service"
"github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global" "github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/common" "github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/files"
) )
type ssl struct { type ssl struct {
@ -23,7 +19,6 @@ func NewSSLJob() *ssl {
} }
func (ssl *ssl) Run() { func (ssl *ssl) Run() {
systemSSLEnable, auto, sslID := service.GetSystemSSL()
sslRepo := repo.NewISSLRepo() sslRepo := repo.NewISSLRepo()
sslService := service.NewIWebsiteSSLService() sslService := service.NewIWebsiteSSLService()
sslList, _ := sslRepo.List() sslList, _ := sslRepo.List()
@ -59,22 +54,6 @@ func (ssl *ssl) Run() {
continue continue
} }
} }
if systemSSLEnable && sslID == s.ID {
websiteSSL, _ := sslRepo.GetFirst(repo.NewCommonRepo().WithByID(s.ID))
fileOp := files.NewFileOp()
secretDir := path.Join(global.CONF.System.BaseDir, "1panel/secret")
if err := fileOp.WriteFile(path.Join(secretDir, "server.crt"), strings.NewReader(websiteSSL.Pem), 0600); err != nil {
global.LOG.Errorf("Failed to update the SSL certificate File for 1Panel System domain [%s] , err:%s", s.PrimaryDomain, err.Error())
continue
}
if err := fileOp.WriteFile(path.Join(secretDir, "server.key"), strings.NewReader(websiteSSL.PrivateKey), 0600); err != nil {
global.LOG.Errorf("Failed to update the SSL certificate for 1Panel System domain [%s] , err:%s", s.PrimaryDomain, err.Error())
continue
}
if auto {
_, _ = cmd.Exec("systemctl restart 1panel.service")
}
}
global.LOG.Infof("The SSL certificate for the [%s] domain has been successfully updated", s.PrimaryDomain) global.LOG.Infof("The SSL certificate for the [%s] domain has been successfully updated", s.PrimaryDomain)
} }
} }

@ -127,6 +127,8 @@ ErrDefaultCA: "The default organization cannot be deleted"
ApplyWebSiteSSLLog: "Start updating {{ .name }} website certificate" ApplyWebSiteSSLLog: "Start updating {{ .name }} website certificate"
ErrUpdateWebsiteSSL: "{{ .name }} website failed to update certificate: {{ .err }}" ErrUpdateWebsiteSSL: "{{ .name }} website failed to update certificate: {{ .err }}"
ApplyWebSiteSSLSuccess: "Update website certificate successfully" ApplyWebSiteSSLSuccess: "Update website certificate successfully"
StartUpdateSystemSSL: "Start updating system certificate"
UpdateSystemSSLSuccess: "Update system certificate successfully"
#mysql #mysql
ErrUserIsExist: "The current user already exists. Please enter a new user" ErrUserIsExist: "The current user already exists. Please enter a new user"

@ -126,6 +126,8 @@ ErrDefaultCA: "默認機構不能刪除"
ApplyWebSiteSSLLog: "開始更新 {{ .name }} 網站憑證" ApplyWebSiteSSLLog: "開始更新 {{ .name }} 網站憑證"
ErrUpdateWebsiteSSL: "{{ .name }} 網站更新憑證失敗: {{ .err }}" ErrUpdateWebsiteSSL: "{{ .name }} 網站更新憑證失敗: {{ .err }}"
ApplyWebSiteSSLSuccess: "更新網站憑證成功" ApplyWebSiteSSLSuccess: "更新網站憑證成功"
StartUpdateSystemSSL: "開始更新系統證書"
UpdateSystemSSLSuccess: "更新系統證書成功"
#mysql #mysql

@ -130,6 +130,8 @@ ApplyWebSiteSSLSuccess: "更新网站证书成功"
ErrExecShell: "执行脚本失败 {{ .err }}" ErrExecShell: "执行脚本失败 {{ .err }}"
ExecShellStart: "开始执行脚本" ExecShellStart: "开始执行脚本"
ExecShellSuccess: "脚本执行成功" ExecShellSuccess: "脚本执行成功"
StartUpdateSystemSSL: "开始更新系统证书"
UpdateSystemSSLSuccess: "更新系统证书成功"
#mysql #mysql
ErrUserIsExist: "当前用户已存在,请重新输入" ErrUserIsExist: "当前用户已存在,请重新输入"

@ -4,13 +4,13 @@ import (
"crypto/tls" "crypto/tls"
"encoding/gob" "encoding/gob"
"fmt" "fmt"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/i18n"
"net" "net"
"net/http" "net/http"
"os" "os"
"path" "path"
"github.com/1Panel-dev/1Panel/backend/i18n"
"github.com/1Panel-dev/1Panel/backend/init/app" "github.com/1Panel-dev/1Panel/backend/init/app"
"github.com/1Panel-dev/1Panel/backend/init/business" "github.com/1Panel-dev/1Panel/backend/init/business"
@ -81,12 +81,16 @@ func Start() {
if err != nil { if err != nil {
panic(err) panic(err)
} }
constant.CertStore.Store(&cert)
server.TLSConfig = &tls.Config{ server.TLSConfig = &tls.Config{
Certificates: []tls.Certificate{cert}, GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
return constant.CertStore.Load().(*tls.Certificate), nil
},
} }
global.LOG.Infof("listen at https://%s:%s [%s]", global.CONF.System.BindAddress, global.CONF.System.Port, tcpItem) global.LOG.Infof("listen at https://%s:%s [%s]", global.CONF.System.BindAddress, global.CONF.System.Port, tcpItem)
if err := server.ServeTLS(tcpKeepAliveListener{ln.(*net.TCPListener)}, certPath, keyPath); err != nil { if err := server.ServeTLS(tcpKeepAliveListener{ln.(*net.TCPListener)}, "", ""); err != nil {
panic(err) panic(err)
} }
} else { } else {

@ -29,7 +29,6 @@ export namespace Setting {
bindAddress: string; bindAddress: string;
ssl: string; ssl: string;
sslType: string; sslType: string;
autoRestart: string;
allowIPs: string; allowIPs: string;
bindDomain: string; bindDomain: string;
securityEntrance: string; securityEntrance: string;

@ -1551,11 +1551,6 @@ const message = {
bindDomain: 'Bind Domain', bindDomain: 'Bind Domain',
unBindDomain: 'Unbind domain', unBindDomain: 'Unbind domain',
panelSSL: 'Panel SSL', panelSSL: 'Panel SSL',
sslAutoRestart: 'Restart 1Panel service after certificate auto-renewal',
sslChangeHelper1:
'Currently, automatic restart of 1Panel service is not selected. The certificate auto-renewal will not take effect immediately and will still require a manual restart of 1Panel.',
sslChangeHelper2:
'The 1Panel service will automatically restart after setting the panel SSL. Do you want to continue?',
unBindDomainHelper: unBindDomainHelper:
'The action of unbinding a domain name may cause system insecurity. Do you want to continue?', 'The action of unbinding a domain name may cause system insecurity. Do you want to continue?',
bindDomainHelper: bindDomainHelper:

@ -1494,9 +1494,6 @@ const message = {
bindDomain: '', bindDomain: '',
unBindDomain: '', unBindDomain: '',
panelSSL: ' SSL', panelSSL: ' SSL',
sslAutoRestart: ' 1Panel ',
sslChangeHelper1: ' 1Panel 1Panel',
sslChangeHelper2: ' SSL 1Panel ',
unBindDomainHelper: '', unBindDomainHelper: '',
bindDomainHelper: ' 1Panel ', bindDomainHelper: ' 1Panel ',
bindDomainHelper1: '', bindDomainHelper1: '',

@ -1496,9 +1496,6 @@ const message = {
bindDomain: '', bindDomain: '',
unBindDomain: '', unBindDomain: '',
panelSSL: ' SSL', panelSSL: ' SSL',
sslAutoRestart: ' 1Panel ',
sslChangeHelper1: ' 1Panel 1Panel',
sslChangeHelper2: ' SSL 1Panel ',
unBindDomainHelper: '', unBindDomainHelper: '',
bindDomainHelper: '访 1Panel ', bindDomainHelper: '访 1Panel ',
bindDomainHelper1: '', bindDomainHelper1: '',

@ -34,7 +34,7 @@ const settingRouter = {
hidden: true, hidden: true,
meta: { meta: {
requiresAuth: true, requiresAuth: true,
activeMenu: 'Setting', activeMenu: '/settings',
}, },
}, },
{ {
@ -44,7 +44,7 @@ const settingRouter = {
hidden: true, hidden: true,
meta: { meta: {
requiresAuth: true, requiresAuth: true,
activeMenu: 'Setting', activeMenu: '/settings',
}, },
}, },
{ {
@ -54,7 +54,7 @@ const settingRouter = {
hidden: true, hidden: true,
meta: { meta: {
requiresAuth: true, requiresAuth: true,
activeMenu: 'Setting', activeMenu: '/settings',
}, },
}, },
{ {
@ -64,7 +64,7 @@ const settingRouter = {
hidden: true, hidden: true,
meta: { meta: {
requiresAuth: true, requiresAuth: true,
activeMenu: 'Setting', activeMenu: '/settings',
}, },
}, },
{ {
@ -74,7 +74,7 @@ const settingRouter = {
component: () => import('@/views/setting/snapshot/index.vue'), component: () => import('@/views/setting/snapshot/index.vue'),
meta: { meta: {
requiresAuth: true, requiresAuth: true,
activeMenu: 'Setting', activeMenu: '/settings',
}, },
}, },
{ {
@ -84,7 +84,7 @@ const settingRouter = {
component: () => import('@/views/setting/expired.vue'), component: () => import('@/views/setting/expired.vue'),
meta: { meta: {
requiresAuth: true, requiresAuth: true,
activeMenu: 'Expired', activeMenu: '/settings',
}, },
}, },
], ],

@ -225,7 +225,6 @@ const form = reactive({
bindAddress: '', bindAddress: '',
ssl: 'disable', ssl: 'disable',
sslType: 'self', sslType: 'self',
autoRestart: 'disable',
securityEntrance: '', securityEntrance: '',
expirationDays: 0, expirationDays: 0,
expirationTime: '', expirationTime: '',
@ -250,7 +249,6 @@ const search = async () => {
if (form.ssl === 'enable') { if (form.ssl === 'enable') {
loadInfo(); loadInfo();
} }
form.autoRestart = res.data.autoRestart;
form.securityEntrance = res.data.securityEntrance; form.securityEntrance = res.data.securityEntrance;
form.expirationDays = Number(res.data.expirationDays); form.expirationDays = Number(res.data.expirationDays);
form.expirationTime = res.data.expirationTime; form.expirationTime = res.data.expirationTime;
@ -330,7 +328,6 @@ const handleSSL = async () => {
ssl: form.ssl, ssl: form.ssl,
sslType: form.sslType, sslType: form.sslType,
sslInfo: sslInfo.value, sslInfo: sslInfo.value,
autoRestart: form.autoRestart,
}; };
sslRef.value!.acceptParams(params); sslRef.value!.acceptParams(params);
return; return;

@ -112,14 +112,6 @@
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
</div> </div>
<el-form-item v-if="form.sslType !== 'import'">
<el-checkbox true-value="enable" false-value="disable" v-model="form.autoRestart">
{{ $t('setting.sslAutoRestart') }}
</el-checkbox>
<span v-if="form.autoRestart === 'disable'" class="input-help">
{{ $t('setting.sslChangeHelper1') }}
</span>
</el-form-item>
</el-col> </el-col>
</el-row> </el-row>
</el-form> </el-form>
@ -143,7 +135,7 @@ import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message'; import { MsgSuccess } from '@/utils/message';
import { downloadSSL, updateSSL } from '@/api/modules/setting'; import { downloadSSL, updateSSL } from '@/api/modules/setting';
import { Rules } from '@/global/form-rules'; import { Rules } from '@/global/form-rules';
import { ElMessageBox, FormInstance } from 'element-plus'; import { FormInstance } from 'element-plus';
import { Setting } from '@/api/interface/setting'; import { Setting } from '@/api/interface/setting';
import DrawerHeader from '@/components/drawer-header/index.vue'; import DrawerHeader from '@/components/drawer-header/index.vue';
import { GlobalStore } from '@/store'; import { GlobalStore } from '@/store';
@ -162,7 +154,6 @@ const form = reactive({
key: '', key: '',
rootPath: '', rootPath: '',
timeout: '', timeout: '',
autoRestart: 'disable',
}); });
const rules = reactive({ const rules = reactive({
@ -179,7 +170,6 @@ const itemSSL = ref();
interface DialogProps { interface DialogProps {
sslType: string; sslType: string;
sslInfo?: Setting.SSLInfo; sslInfo?: Setting.SSLInfo;
autoRestart: string;
} }
const acceptParams = async (params: DialogProps): Promise<void> => { const acceptParams = async (params: DialogProps): Promise<void> => {
if (params.sslType.indexOf('-') !== -1) { if (params.sslType.indexOf('-') !== -1) {
@ -202,7 +192,6 @@ const acceptParams = async (params: DialogProps): Promise<void> => {
} else { } else {
loadSSLs(); loadSSLs();
} }
form.autoRestart = params.autoRestart;
drawerVisible.value = true; drawerVisible.value = true;
}; };
const emit = defineEmits<{ (e: 'search'): void }>(); const emit = defineEmits<{ (e: 'search'): void }>();
@ -243,41 +232,31 @@ const onSaveSSL = async (formEl: FormInstance | undefined) => {
if (!formEl) return; if (!formEl) return;
formEl.validate(async (valid) => { formEl.validate(async (valid) => {
if (!valid) return; if (!valid) return;
let msg = !form.autoRestart let itemType = form.sslType;
? i18n.global.t('setting.sslChangeHelper1') + '\n\n\n' + 'qwdqwdqwd' if (form.sslType === 'import') {
: i18n.global.t('setting.sslChangeHelper2'); itemType = form.itemSSLType === 'paste' ? 'import-paste' : 'import-local';
ElMessageBox.confirm(msg, i18n.global.t('setting.panelSSL'), { }
confirmButtonText: i18n.global.t('commons.button.confirm'), let param = {
cancelButtonText: i18n.global.t('commons.button.cancel'), ssl: 'enable',
type: 'info', sslType: itemType,
}).then(async () => { domain: '',
let itemType = form.sslType; sslID: form.sslID,
if (form.sslType === 'import') { cert: form.cert,
itemType = form.itemSSLType === 'paste' ? 'import-paste' : 'import-local'; key: form.key,
} };
let param = { let href = window.location.href;
ssl: 'enable', param.domain = href.split('//')[1].split(':')[0];
sslType: itemType, await updateSSL(param).then(() => {
domain: '', MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
sslID: form.sslID,
cert: form.cert,
key: form.key,
autoRestart: form.autoRestart,
};
let href = window.location.href; let href = window.location.href;
param.domain = href.split('//')[1].split(':')[0]; globalStore.isLogin = false;
await updateSSL(param).then(() => { let address = href.split('://')[1];
MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); if (globalStore.entrance) {
let href = window.location.href; address = address.replaceAll('settings/safe', globalStore.entrance);
globalStore.isLogin = false; } else {
let address = href.split('://')[1]; address = address.replaceAll('settings/safe', 'login');
if (globalStore.entrance) { }
address = address.replaceAll('settings/safe', globalStore.entrance); window.open(`https://${address}`, '_self');
} else {
address = address.replaceAll('settings/safe', 'login');
}
window.open(`https://${address}`, '_self');
});
}); });
}); });
}; };

@ -15,7 +15,7 @@ require (
github.com/gin-contrib/gzip v0.0.6 github.com/gin-contrib/gzip v0.0.6
github.com/gin-gonic/gin v1.9.1 github.com/gin-gonic/gin v1.9.1
github.com/glebarez/sqlite v1.10.0 github.com/glebarez/sqlite v1.10.0
github.com/go-acme/lego/v4 v4.20.2 github.com/go-acme/lego/v4 v4.20.4
github.com/go-gormigrate/gormigrate/v2 v2.1.1 github.com/go-gormigrate/gormigrate/v2 v2.1.1
github.com/go-playground/validator/v10 v10.18.0 github.com/go-playground/validator/v10 v10.18.0
github.com/go-redis/redis v6.15.9+incompatible github.com/go-redis/redis v6.15.9+incompatible

@ -318,8 +318,8 @@ github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
github.com/glebarez/sqlite v1.10.0 h1:u4gt8y7OND/cCei/NMHmfbLxF6xP2wgKcT/BJf2pYkc= github.com/glebarez/sqlite v1.10.0 h1:u4gt8y7OND/cCei/NMHmfbLxF6xP2wgKcT/BJf2pYkc=
github.com/glebarez/sqlite v1.10.0/go.mod h1:IJ+lfSOmiekhQsFTJRx/lHtGYmCdtAiTaf5wI9u5uHA= github.com/glebarez/sqlite v1.10.0/go.mod h1:IJ+lfSOmiekhQsFTJRx/lHtGYmCdtAiTaf5wI9u5uHA=
github.com/go-acme/lego/v4 v4.20.2 h1:ZwO3oLZb8fL6up1OZVJP3yHuvqhozzlEmyqKmhrPchQ= github.com/go-acme/lego/v4 v4.20.4 h1:yCQGBX9jOfMbriEQUocdYm7EBapdTp8nLXYG8k6SqSU=
github.com/go-acme/lego/v4 v4.20.2/go.mod h1:foauPlhnhoq8WUphaWx5U04uDc+JGhk4ZZtPz/Vqsjg= github.com/go-acme/lego/v4 v4.20.4/go.mod h1:foauPlhnhoq8WUphaWx5U04uDc+JGhk4ZZtPz/Vqsjg=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=

Loading…
Cancel
Save