diff --git a/internal/const/const.go b/internal/const/const.go index 64153265..32a533de 100644 --- a/internal/const/const.go +++ b/internal/const/const.go @@ -13,6 +13,4 @@ const ( EncryptMethod = "aes-256-cfb" ErrServer = "服务器出了点小问题,请联系技术人员处理。" - - EnabledFrame = false // 是否允许被嵌入到别的网站中 TODO 做成可配置 ) diff --git a/internal/securitymanager/security_config.go b/internal/securitymanager/security_config.go new file mode 100644 index 00000000..15b15eaa --- /dev/null +++ b/internal/securitymanager/security_config.go @@ -0,0 +1,98 @@ +package securitymanager + +import ( + "encoding/json" + "github.com/TeaOSLab/EdgeAdmin/internal/rpc" + "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" + "github.com/iwind/TeaGo/logs" + "reflect" + "sync" +) + +var locker sync.Mutex + +const ( + SecuritySettingName = "adminSecurityConfig" + + FrameNone = "" + FrameDeny = "DENY" + FrameSameOrigin = "SAMEORIGIN" +) + +var sharedSecurityConfig *SecurityConfig = nil + +type SecurityConfig struct { + Frame string `json:"frame"` +} + +func LoadSecurityConfig() (*SecurityConfig, error) { + locker.Lock() + defer locker.Unlock() + + config, err := loadSecurityConfig() + if err != nil { + return nil, err + } + + v := reflect.Indirect(reflect.ValueOf(config)).Interface().(SecurityConfig) + return &v, nil +} + +func UpdateSecurityConfig(securityConfig *SecurityConfig) error { + locker.Lock() + defer locker.Unlock() + + var rpcClient, err = rpc.SharedRPC() + if err != nil { + return err + } + valueJSON, err := json.Marshal(securityConfig) + if err != nil { + return err + } + _, err = rpcClient.SysSettingRPC().UpdateSysSetting(rpcClient.Context(1), &pb.UpdateSysSettingRequest{ + Code: SecuritySettingName, + ValueJSON: valueJSON, + }) + if err != nil { + return err + } + sharedSecurityConfig = securityConfig + return nil +} + +func loadSecurityConfig() (*SecurityConfig, error) { + if sharedSecurityConfig != nil { + return sharedSecurityConfig, nil + } + var rpcClient, err = rpc.SharedRPC() + if err != nil { + return nil, err + } + resp, err := rpcClient.SysSettingRPC().ReadSysSetting(rpcClient.Context(1), &pb.ReadSysSettingRequest{ + Code: SecuritySettingName, + }) + if err != nil { + return nil, err + } + if len(resp.ValueJSON) == 0 { + sharedSecurityConfig = defaultSecurityConfig() + return sharedSecurityConfig, nil + } + + config := &SecurityConfig{} + err = json.Unmarshal(resp.ValueJSON, config) + if err != nil { + logs.Println("[SECURITY_MANAGER]" + err.Error()) + sharedSecurityConfig = defaultSecurityConfig() + return sharedSecurityConfig, nil + } + sharedSecurityConfig = config + return sharedSecurityConfig, nil +} + +func defaultSecurityConfig() *SecurityConfig { + return &SecurityConfig{ + Frame: FrameSameOrigin, + } +} diff --git a/internal/securitymanager/security_config_test.go b/internal/securitymanager/security_config_test.go new file mode 100644 index 00000000..5927bdc2 --- /dev/null +++ b/internal/securitymanager/security_config_test.go @@ -0,0 +1,32 @@ +package securitymanager + +import ( + _ "github.com/iwind/TeaGo/bootstrap" + "testing" + "time" +) + +func TestLoadSecurityConfig(t *testing.T) { + for i := 0; i < 10; i++ { + before := time.Now() + config, err := LoadSecurityConfig() + if err != nil { + t.Fatal(err) + } + t.Log(time.Since(before).Seconds()*1000, "ms") + t.Logf("%p", config) + } +} + +func TestLoadSecurityConfig2(t *testing.T) { + for i := 0; i < 10; i++ { + config, err := LoadSecurityConfig() + if err != nil { + t.Fatal(err) + } + if i == 0 { + config.Frame = "DENY" + } + t.Log(config.Frame) + } +} diff --git a/internal/web/actions/default/settings/security/index.go b/internal/web/actions/default/settings/security/index.go index 837df198..99a26b7f 100644 --- a/internal/web/actions/default/settings/security/index.go +++ b/internal/web/actions/default/settings/security/index.go @@ -1,6 +1,10 @@ package security -import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" +import ( + "github.com/TeaOSLab/EdgeAdmin/internal/securitymanager" + "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" + "github.com/iwind/TeaGo/actions" +) type IndexAction struct { actionutils.ParentAction @@ -11,5 +15,35 @@ func (this *IndexAction) Init() { } func (this *IndexAction) RunGet(params struct{}) { + config, err := securitymanager.LoadSecurityConfig() + if err != nil { + this.ErrorPage(err) + return + } + this.Data["config"] = config this.Show() } + +func (this *IndexAction) RunPost(params struct { + Frame string + + Must *actions.Must + CSRF *actionutils.CSRF +}) { + defer this.CreateLogInfo("修改管理界面安全设置") + + config, err := securitymanager.LoadSecurityConfig() + if err != nil { + this.ErrorPage(err) + return + } + + config.Frame = params.Frame + err = securitymanager.UpdateSecurityConfig(config) + if err != nil { + this.ErrorPage(err) + return + } + + this.Success() +} diff --git a/internal/web/actions/default/settings/security/init.go b/internal/web/actions/default/settings/security/init.go index 7b6a4d13..accd2ce0 100644 --- a/internal/web/actions/default/settings/security/init.go +++ b/internal/web/actions/default/settings/security/init.go @@ -12,7 +12,7 @@ func init() { Helper(helpers.NewUserMustAuth()). Helper(settingutils.NewHelper("security")). Prefix("/settings/security"). - Get("", new(IndexAction)). + GetPost("", new(IndexAction)). EndAll() }) } diff --git a/internal/web/helpers/user_must_auth.go b/internal/web/helpers/user_must_auth.go index fa528a95..bdea340a 100644 --- a/internal/web/helpers/user_must_auth.go +++ b/internal/web/helpers/user_must_auth.go @@ -3,6 +3,7 @@ package helpers import ( teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const" nodes "github.com/TeaOSLab/EdgeAdmin/internal/rpc" + "github.com/TeaOSLab/EdgeAdmin/internal/securitymanager" "github.com/TeaOSLab/EdgeAdmin/internal/setup" "github.com/TeaOSLab/EdgeAdmin/internal/utils" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" @@ -26,8 +27,11 @@ func (this *UserMustAuth) BeforeAction(actionPtr actions.ActionWrapper, paramNam var action = actionPtr.Object() // 安全相关 - if !teaconst.EnabledFrame { + securityConfig, _ := securitymanager.LoadSecurityConfig() + if securityConfig == nil { action.AddHeader("X-Frame-Options", "SAMEORIGIN") + } else if len(securityConfig.Frame) > 0 { + action.AddHeader("X-Frame-Options", securityConfig.Frame) } action.AddHeader("Content-Security-Policy", "default-src 'self' data:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'") diff --git a/internal/web/helpers/user_should_auth.go b/internal/web/helpers/user_should_auth.go index 0f022d4f..54bc9f0a 100644 --- a/internal/web/helpers/user_should_auth.go +++ b/internal/web/helpers/user_should_auth.go @@ -1,7 +1,7 @@ package helpers import ( - teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const" + "github.com/TeaOSLab/EdgeAdmin/internal/securitymanager" "github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils" "github.com/iwind/TeaGo/actions" "net/http" @@ -16,8 +16,11 @@ func (this *UserShouldAuth) BeforeAction(actionPtr actions.ActionWrapper, paramN // 安全相关 action := this.action - if !teaconst.EnabledFrame { + securityConfig, _ := securitymanager.LoadSecurityConfig() + if securityConfig == nil { action.AddHeader("X-Frame-Options", "SAMEORIGIN") + } else if len(securityConfig.Frame) > 0 { + action.AddHeader("X-Frame-Options", securityConfig.Frame) } action.AddHeader("Content-Security-Policy", "default-src 'self' data:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'") diff --git a/web/views/@default/settings/security/index.html b/web/views/@default/settings/security/index.html index 79a92a06..c2081fcf 100644 --- a/web/views/@default/settings/security/index.html +++ b/web/views/@default/settings/security/index.html @@ -1,3 +1,20 @@ {$layout} -

此功能暂未开放,敬请期待。

\ No newline at end of file +
+
+ + + + + + +
Frame嵌套条件 + +

当前服务被别的网页框架嵌套的条件限制。

+
+ +
\ No newline at end of file diff --git a/web/views/@default/settings/security/index.js b/web/views/@default/settings/security/index.js new file mode 100644 index 00000000..295a9aaf --- /dev/null +++ b/web/views/@default/settings/security/index.js @@ -0,0 +1,3 @@ +Tea.context(function () { + this.success = NotifyReloadSuccess("保存成功") +}) \ No newline at end of file