//Copyright 2017-2019 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package conf
import (
"rasp-cloud/tools"
"github.com/astaxie/beego"
)
const (
StartTypeForeground = "panel"
StartTypeAgent = "agent"
StartTypeReset = "reset"
StartTypeDefault = "default"
)
type RaspAppConfig struct {
EsAddr string
EsUser string
EsPwd string
MongoDBAddr string
MongoDBUser string
MongoDBPwd string
MongoDBName string
MongoDBPoolLimit int
MaxPlugins int
AlarmLogMode string
AlarmBufferSize int
AlarmCheckInterval int64
CookieLifeTime int
RegisterCallbackUrl string
RegisterCallbackToken string
Flag *Flag
}
type Flag struct {
StartType *string
Password *string
Daemon *bool
Version *bool
}
var (
AppConfig = &RaspAppConfig{}
)
func InitConfig(startFlag *Flag) {
AppConfig.Flag = startFlag
AppConfig.EsAddr = beego.AppConfig.String("EsAddr")
AppConfig.EsUser = beego.AppConfig.DefaultString("EsUser", "")
AppConfig.EsPwd = beego.AppConfig.DefaultString("EsPwd", "")
AppConfig.MongoDBAddr = beego.AppConfig.DefaultString("MongoDBAddr", "")
AppConfig.MongoDBPoolLimit = beego.AppConfig.DefaultInt("MongoDBPoolLimit", 1024)
AppConfig.MongoDBName = beego.AppConfig.DefaultString("MongoDBName", "openrasp")
AppConfig.MongoDBUser = beego.AppConfig.DefaultString("MongoDBUser", "")
AppConfig.MongoDBPwd = beego.AppConfig.DefaultString("MongoDBPwd", "")
AppConfig.MaxPlugins = beego.AppConfig.DefaultInt("MaxPlugins", 30)
AppConfig.AlarmLogMode = beego.AppConfig.DefaultString("AlarmLogMode", "file")
AppConfig.AlarmBufferSize = beego.AppConfig.DefaultInt("AlarmBufferSize", 300)
AppConfig.AlarmCheckInterval = beego.AppConfig.DefaultInt64("AlarmCheckInterval", 120)
AppConfig.CookieLifeTime = beego.AppConfig.DefaultInt("CookieLifeTime", 7*24)
AppConfig.RegisterCallbackUrl = beego.AppConfig.DefaultString("RegisterCallbackUrl", "")
AppConfig.RegisterCallbackToken = beego.AppConfig.DefaultString("RegisterCallbackToken", "")
ValidRaspConf(AppConfig)
}
func ValidRaspConf(config *RaspAppConfig) {
if config.EsAddr == "" {
failLoadConfig("the 'EsAddr' config item in app.conf can not be empty")
}
if config.MongoDBAddr == "" {
failLoadConfig("the 'MongoDBAddr' config item in app.conf can not be empty")
}
if config.MongoDBPoolLimit <= 0 {
failLoadConfig("the 'poolLimit' config must be greater than 0")
} else if config.MongoDBPoolLimit < 10 {
beego.Warning("the value of 'poolLimit' config is less than 10, it will be set to 10")
config.MongoDBPoolLimit = 10
}
if config.MaxPlugins <= 0 {
failLoadConfig("the 'MaxPlugins' config must be greater than 0")
} else if config.MaxPlugins < 10 {
beego.Warning("the value of 'MaxPlugins' config is less than 10, it will be set to 10")
config.MaxPlugins = 10
}
if config.AlarmBufferSize <= 0 {
failLoadConfig("the 'AlarmBufferSize' config must be greater than 0")
} else if config.AlarmBufferSize < 100 {
beego.Warning("the value of 'AlarmBufferSize' config is less than 100, it will be set to 100")
config.AlarmBufferSize = 100
}
if config.AlarmCheckInterval <= 0 {
failLoadConfig("the 'AlarmCheckInterval' config must be greater than 0")
} else if config.AlarmCheckInterval < 10 {
beego.Warning("the value of 'AlarmCheckInterval' config is less than 10, it will be set to 10")
config.AlarmCheckInterval = 10
}
if config.CookieLifeTime <= 0 {
failLoadConfig("the 'CookieLifeTime' config must be greater than 0")
}
}
func failLoadConfig(msg string) {
tools.Panic(tools.ErrCodeConfigInitFailed, msg, nil)
}
//Copyright 2017-2019 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package agent_logs
import (
"rasp-cloud/controllers"
"rasp-cloud/models/logs"
"time"
)
// Operations about attack alarm message
type AttackAlarmController struct {
controllers.BaseController
}
// @router / [post]
func (o *AttackAlarmController) Post() {
var alarms []map[string]interface{}
o.UnmarshalJson(&alarms)
count := 0
for _, alarm := range alarms {
alarm["@timestamp"] = time.Now().UnixNano() / 1000000
err := logs.AddAttackAlarm(alarm)
if err == nil {
count++
}
}
o.Serve(map[string]uint64{"count": uint64(count)})
}
//Copyright 2017-2018 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package agent_logs
import (
"rasp-cloud/controllers"
"rasp-cloud/models/logs"
"time"
)
type ErrorController struct {
controllers.BaseController
}
// @router / [post]
func (o *ErrorController) Post() {
var alarms []map[string]interface{}
o.UnmarshalJson(&alarms)
count := 0
for _, alarm := range alarms {
alarm["@timestamp"] = time.Now().UnixNano() / 1000000
err := logs.AddErrorAlarm(alarm)
if err == nil {
count++
}
}
o.Serve(map[string]uint64{"count": uint64(count)})
}
//Copyright 2017-2019 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package agent_logs
import (
"rasp-cloud/controllers"
"rasp-cloud/models/logs"
"time"
)
// Operations about policy alarm message
type PolicyAlarmController struct {
controllers.BaseController
}
// @router / [post]
func (o *PolicyAlarmController) Post() {
var alarms []map[string]interface{}
o.UnmarshalJson(&alarms)
count := 0
for _, alarm := range alarms {
alarm["@timestamp"] = time.Now().UnixNano() / 1000000
err := logs.AddPolicyAlarm(alarm)
if err == nil {
count++
}
}
o.Serve(map[string]uint64{"count": uint64(count)})
}
//Copyright 2017-2019 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package agent
import (
"gopkg.in/mgo.v2"
"net/http"
"rasp-cloud/controllers"
"rasp-cloud/models"
"time"
)
// Operations about plugin
type HeartbeatController struct {
controllers.BaseController
}
type heartbeatParam struct {
RaspId string `json:"rasp_id"`
PluginVersion string `json:"plugin_version"`
PluginMd5 string `json:"plugin_md5"`
ConfigTime int64 `json:"config_time"`
}
// @router / [post]
func (o *HeartbeatController) Post() {
var heartbeat heartbeatParam
o.UnmarshalJson(&heartbeat)
rasp, err := models.GetRaspById(heartbeat.RaspId)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to get rasp", err)
}
rasp.LastHeartbeatTime = time.Now().Unix()
rasp.PluginVersion = heartbeat.PluginVersion
err = models.UpsertRaspById(heartbeat.RaspId, rasp)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to update rasp", err)
}
pluginMd5 := heartbeat.PluginMd5
configTime := heartbeat.ConfigTime
appId := o.Ctx.Input.Header("X-OpenRASP-AppID")
app, err := models.GetAppById(appId)
if err != nil || app == nil {
o.ServeError(http.StatusBadRequest, "cannot get the app", err)
}
result := make(map[string]interface{})
isUpdate := false
// handle plugin
selectedPlugin, err := models.GetSelectedPlugin(appId, true)
if err != nil && err != mgo.ErrNotFound {
o.ServeError(http.StatusBadRequest, "failed to get selected plugin", err)
}
if selectedPlugin != nil {
if pluginMd5 != selectedPlugin.Md5 {
isUpdate = true
}
if app.ConfigTime > 0 && app.ConfigTime > int64(configTime) {
isUpdate = true
}
}
if isUpdate {
whitelistConfig := make(map[string]interface{})
for _, configItem := range app.WhitelistConfig {
whiteHookTypes := make([]string, 0, len(configItem.Hook))
for hookType, isWhite := range configItem.Hook {
if isWhite {
whiteHookTypes = append(whiteHookTypes, hookType)
}
}
whitelistConfig[configItem.Url] = whiteHookTypes
}
//app.GeneralConfig["algorithm.config"] = selectedPlugin.AlgorithmConfig
app.GeneralConfig["hook.white"] = whitelistConfig
result["plugin"] = selectedPlugin
result["config_time"] = app.ConfigTime
result["config"] = app.GeneralConfig
}
o.Serve(result)
}
//Copyright 2017-2019 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package agent
import (
"github.com/astaxie/beego/validation"
"net/http"
"rasp-cloud/controllers"
"rasp-cloud/models"
"time"
"rasp-cloud/conf"
"github.com/astaxie/beego"
)
type RaspController struct {
controllers.BaseController
}
// @router / [post]
func (o *RaspController) Post() {
var rasp = &models.Rasp{}
rasp.AppId = o.Ctx.Input.Header("X-OpenRASP-AppID")
o.UnmarshalJson(rasp)
if rasp.Id == "" {
o.ServeError(http.StatusBadRequest, "rasp id cannot be empty")
}
if len(rasp.Id) < 16 || len(rasp.Id) > 512 {
o.ServeError(http.StatusBadRequest, "the length of rasp id must be between 16~512")
}
if rasp.Version == "" {
o.ServeError(http.StatusBadRequest, "rasp_version cannot be empty")
}
if len(rasp.Version) >= 50 {
o.ServeError(http.StatusBadRequest, "the length of rasp version must be less than 50")
}
if rasp.HostName == "" {
o.ServeError(http.StatusBadRequest, "rasp hostname cannot be empty")
}
if len(rasp.HostName) >= 1024 {
o.ServeError(http.StatusBadRequest, "the length of rasp hostname must be less than 1024")
}
if rasp.LanguageVersion == "" {
o.ServeError(http.StatusBadRequest, "rasp language_version cannot be empty")
}
if len(rasp.LanguageVersion) >= 50 {
o.ServeError(http.StatusBadRequest, "the length of rasp language version must be less than 50")
}
if rasp.Language == "" {
o.ServeError(http.StatusBadRequest, "rasp language cannot be empty")
}
if len(rasp.Language) >= 50 {
o.ServeError(http.StatusBadRequest, "the length of rasp language must be less than 50")
}
if len(rasp.ServerType) >= 256 {
o.ServeError(http.StatusBadRequest, "the length of rasp server type must be less than 256")
}
if len(rasp.ServerVersion) >= 50 {
o.ServeError(http.StatusBadRequest, "the length of rasp server version must be less than 50")
}
if rasp.RegisterIp != "" {
valid := validation.Validation{}
if result := valid.IP(rasp.RegisterIp, "IP"); !result.Ok {
o.ServeError(http.StatusBadRequest, "rasp register_ip format error: "+result.Error.Message)
}
}
if rasp.HeartbeatInterval <= 0 {
o.ServeError(http.StatusBadRequest, "heartbeat_interval must be greater than 0")
}
if rasp.Environ == nil {
rasp.Environ = map[string]string{}
}
for k, v := range rasp.Environ {
if len(k) > 4096 {
o.ServeError(http.StatusBadRequest,
"the length of environ key cannot be greater than 4096")
}
if len(v) > 4096 {
o.ServeError(http.StatusBadRequest,
"the length of environ value cannot be greater than 4096")
}
}
rasp.LastHeartbeatTime = time.Now().Unix()
rasp.RegisterTime = time.Now().Unix()
err := models.UpsertRaspById(rasp.Id, rasp)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to add rasp", err)
}
if len(conf.AppConfig.RegisterCallbackUrl) > 0 {
go func() {
err = models.RegisterCallback(conf.AppConfig.RegisterCallbackUrl, conf.AppConfig.RegisterCallbackToken, rasp)
if err != nil {
beego.Error("failed to send register callback to url: " +
conf.AppConfig.RegisterCallbackUrl + ", " + err.Error())
}
}()
}
models.AddOperation(rasp.AppId, models.OperationTypeRegisterRasp, o.Ctx.Input.IP(),
"New RASP agent registered from "+rasp.HostName+": "+rasp.Id, "")
o.Serve(rasp)
}
//Copyright 2017-2019 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package agent
import (
"net/http"
"rasp-cloud/controllers"
"rasp-cloud/models"
)
type ReportController struct {
controllers.BaseController
}
// @router / [post]
func (o *ReportController) Post() {
var reportData *models.ReportData
o.UnmarshalJson(&reportData)
if reportData.RaspId == "" {
o.ServeError(http.StatusBadRequest, "rasp_id cannot be empty")
}
rasp, err := models.GetRaspById(reportData.RaspId)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to get rasp", err)
}
if reportData.Time <= 0 {
o.ServeError(http.StatusBadRequest, "time param must be greater than 0")
}
if reportData.RequestSum < 0 {
o.ServeError(http.StatusBadRequest, "request_sum param cannot be less than 0")
}
err = models.AddReportData(reportData, rasp.AppId)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to insert report data", err)
}
o.Serve(reportData)
}
//Copyright 2017-2019 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package api
import (
"encoding/json"
"github.com/astaxie/beego/validation"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
"math"
"net/http"
"rasp-cloud/controllers"
"rasp-cloud/models"
"strconv"
"sync"
"time"
)
// Operations about app
type AppController struct {
controllers.BaseController
}
type pageParam struct {
AppId string `json:"app_id"`
Page int `json:"page"`
Perpage int `json:"perpage"`
}
var (
supportLanguages = []string{"java", "php"}
mutex sync.Mutex
)
// @router /get [post]
func (o *AppController) GetApp() {
var data pageParam
o.UnmarshalJson(&data)
if data.AppId == "" {
o.ValidPage(data.Page, data.Perpage)
var result = make(map[string]interface{})
total, apps, err := models.GetAllApp(data.Page, data.Perpage, true)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to get apps", err)
}
if apps == nil {
apps = make([]*models.App, 0)
}
result["total"] = total
result["total_page"] = math.Ceil(float64(total) / float64(data.Perpage))
result["page"] = data.Page
result["perpage"] = data.Perpage
result["data"] = apps
o.Serve(result)
} else {
app, err := models.GetAppById(data.AppId)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to get app", err)
}
o.Serve(app)
}
}
// @router /rasp/get [post]
func (o *AppController) GetRasps() {
var param pageParam
o.UnmarshalJson(¶m)
o.ValidPage(param.Page, param.Perpage)
app, err := models.GetAppById(param.AppId)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to get app", err)
}
if app == nil {
o.ServeError(http.StatusBadRequest, "the app doesn't exist")
}
var result = make(map[string]interface{})
total, rasps, err := models.GetRaspByAppId(app.Id, param.Page, param.Perpage)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to get apps", err)
}
if rasps == nil {
rasps = make([]*models.Rasp, 0)
}
result["total"] = total
result["total_page"] = math.Ceil(float64(total) / float64(param.Perpage))
result["page"] = param.Page
result["perpage"] = param.Perpage
result["data"] = rasps
o.Serve(result)
}
// @router /secret/get [post]
func (o *AppController) GetAppSecret() {
var param struct {
AppId string `json:"app_id"`
}
o.UnmarshalJson(¶m)
if param.AppId == "" {
o.ServeError(http.StatusBadRequest, "app_id can not be empty")
}
secret, err := models.GetSecretByAppId(param.AppId)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to get secret", err)
}
o.Serve(map[string]string{
"secret": secret,
})
}
// @router /secret/regenerate [post]
func (o *AppController) RegenerateAppSecret() {
var param struct {
AppId string `json:"app_id"`
}
o.UnmarshalJson(¶m)
if param.AppId == "" {
o.ServeError(http.StatusBadRequest, "app_id can not be empty")
}
secret, err := models.RegenerateSecret(param.AppId)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to get secret", err)
}
models.AddOperation(param.AppId, models.OperationTypeRegenerateSecret,
o.Ctx.Input.IP(), "Reset AppSecret of "+param.AppId)
o.Serve(map[string]string{
"secret": secret,
})
}
// @router /general/config [post]
func (o *AppController) UpdateAppGeneralConfig() {
var param struct {
AppId string `json:"app_id"`
Config map[string]interface{} `json:"config"`
}
o.UnmarshalJson(¶m)
if param.AppId == "" {
o.ServeError(http.StatusBadRequest, "app_id can not be empty")
}
if param.Config == nil {
o.ServeError(http.StatusBadRequest, "config can not be empty")
}
o.validateAppConfig(param.Config)
app, err := models.UpdateGeneralConfig(param.AppId, param.Config)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to update app general config", err)
}
models.AddOperation(param.AppId, models.OperationTypeUpdateGenerateConfig,
o.Ctx.Input.IP(), "Updated general config of "+param.AppId)
o.Serve(app)
}
// @router /whitelist/config [post]
func (o *AppController) UpdateAppWhiteListConfig() {
var param struct {
AppId string `json:"app_id"`
Config []models.WhitelistConfigItem `json:"config"`
}
o.UnmarshalJson(¶m)
if param.AppId == "" {
o.ServeError(http.StatusBadRequest, "app_id can not be empty")
}
if param.Config == nil {
o.ServeError(http.StatusBadRequest, "config can not be empty")
}
o.validateWhiteListConfig(param.Config)
app, err := models.UpdateWhiteListConfig(param.AppId, param.Config)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to update app whitelist config", err)
}
models.AddOperation(param.AppId, models.OperationTypeUpdateWhitelistConfig,
o.Ctx.Input.IP(), "Updated whitelist config of "+param.AppId)
o.Serve(app)
}
// @router / [post]
func (o *AppController) Post() {
var app = &models.App{}
o.UnmarshalJson(app)
if app.Name == "" {
o.ServeError(http.StatusBadRequest, "app name cannot be empty")
}
if len(app.Name) > 64 {
o.ServeError(http.StatusBadRequest, "the length of app name cannot be greater than 64")
}
if app.Language == "" {
o.ServeError(http.StatusBadRequest, "app programming language cannot be empty")
}
if len(app.Language) > 64 {
o.ServeError(http.StatusBadRequest, "the length of app language name cannot be greater than 64")
}
languageSupported := false
for _, language := range supportLanguages {
if app.Language == language {
languageSupported = true
break
}
}
if !languageSupported {
o.ServeError(http.StatusBadRequest, "Unsupported programming language: "+app.Language)
}
if len(app.Description) > 1024 {
o.ServeError(http.StatusBadRequest, "the length of the app description can not be greater than 1024")
}
if len(app.SelectedPluginId) > 1024 {
o.ServeError(http.StatusBadRequest, "the length of the app selected_plugin_id can not be greater than 1024")
}
if app.EmailAlarmConf.Enable {
o.validEmailConf(&app.EmailAlarmConf)
}
if app.HttpAlarmConf.Enable {
o.validHttpAlarm(&app.HttpAlarmConf)
}
if app.DingAlarmConf.Enable {
o.validDingConf(&app.DingAlarmConf)
}
if app.GeneralConfig != nil {
o.validateAppConfig(app.GeneralConfig)
configTime := time.Now().UnixNano()
app.ConfigTime = configTime
}
if app.WhitelistConfig != nil {
o.validateWhiteListConfig(app.WhitelistConfig)
configTime := time.Now().UnixNano()
app.ConfigTime = configTime
} else {
app.WhitelistConfig = make([]models.WhitelistConfigItem, 0)
}
app, err := models.AddApp(app)
if err != nil {
o.ServeError(http.StatusBadRequest, "create app failed", err)
}
models.AddOperation(app.Id, models.OperationTypeAddApp, o.Ctx.Input.IP(), "New app created with name "+app.Name)
o.Serve(app)
}
// @router /config [post]
func (o *AppController) ConfigApp() {
var param struct {
AppId string `json:"app_id"`
Language string `json:"language,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
}
o.UnmarshalJson(¶m)
if param.AppId == "" {
o.ServeError(http.StatusBadRequest, "app_id can not be empty")
}
_, err := models.GetAppById(param.AppId)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to get app", err)
}
if param.Name == "" {
o.ServeError(http.StatusBadRequest, "app name cannot be empty")
}
if len(param.Name) > 64 {
o.ServeError(http.StatusBadRequest, "the length of app name cannot be greater than 64")
}
if param.Language == "" {
o.ServeError(http.StatusBadRequest, "app language cannot be empty")
}
if len(param.Language) > 64 {
o.ServeError(http.StatusBadRequest, "the length of app language name cannot be greater than 64")
}
languageSupported := false
for _, language := range supportLanguages {
if param.Language == language {
languageSupported = true
break
}
}
if !languageSupported {
o.ServeError(http.StatusBadRequest, "can not support the language: "+param.Language)
}
if len(param.Description) > 1024 {
o.ServeError(http.StatusBadRequest, "the length of app description can not be greater than 1024")
}
updateData := bson.M{"name": param.Name, "language": param.Language, "description": param.Description}
app, err := models.UpdateAppById(param.AppId, updateData)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to update app config", err)
}
operationData, err := json.Marshal(updateData)
models.AddOperation(app.Id, models.OperationTypeEditApp, o.Ctx.Input.IP(), "Updated app info for "+param.AppId+": "+string(operationData))
o.Serve(app)
}
func (o *AppController) validEmailConf(conf *models.EmailAlarmConf) {
var valid = validation.Validation{}
if conf.ServerAddr == "" {
o.ServeError(http.StatusBadRequest, "the email server_addr cannot be empty")
}
if len(conf.ServerAddr) > 256 {
o.ServeError(http.StatusBadRequest, "the length of email server_addr cannot be greater than 256")
}
if len(conf.From) > 256 {
o.ServeError(http.StatusBadRequest, "the length of from cannot be greater than 256")
}
if len(conf.Subject) > 256 {
o.ServeError(http.StatusBadRequest, "the length of email subject cannot be greater than 256")
}
if len(conf.UserName) > 256 {
o.ServeError(http.StatusBadRequest, "the length of email username cannot be greater than 256")
}
if len(conf.Password) > 256 {
o.ServeError(http.StatusBadRequest, "the length of email password cannot be greater than 256")
}
if len(conf.RecvAddr) == 0 {
o.ServeError(http.StatusBadRequest, "the email recv_addr cannot be empty")
}
if len(conf.RecvAddr) > 128 {
o.ServeError(http.StatusBadRequest, "the count of email recv_addr cannot be greater than 128")
}
conf.RecvAddr = o.validAppArrayParam(conf.RecvAddr, "email recv_addr", valid.Email)
}
func (o *AppController) validDingConf(conf *models.DingAlarmConf) {
if conf.CorpId == "" {
o.ServeError(http.StatusBadRequest, "the ding ding corp_id cannot be empty")
}
if len(conf.CorpId) > 256 {
o.ServeError(http.StatusBadRequest, "the length of ding ding corp_id cannot be greater than 128")
}
if conf.CorpSecret == "" {
o.ServeError(http.StatusBadRequest, "the ding ding corp_secret cannot be empty")
}
if len(conf.CorpSecret) > 256 {
o.ServeError(http.StatusBadRequest, "the length of ding ding corp_secret cannot be greater than 128")
}
if len(conf.RecvParty) == 0 && len(conf.RecvUser) == 0 {
o.ServeError(http.StatusBadRequest, "ding ding recv_party and ding ding recv_user cannot be empty at the same time")
}
if len(conf.RecvParty) > 128 {
o.ServeError(http.StatusBadRequest, "the count of ding ding recv_party cannot be greater than 128")
}
if len(conf.RecvUser) > 128 {
o.ServeError(http.StatusBadRequest, "the count of ding ding recv_user cannot be greater than 128")
}
if conf.AgentId == "" {
o.ServeError(http.StatusBadRequest, "the ding ding agent_id cannot be empty")
}
if len(conf.AgentId) > 256 {
o.ServeError(http.StatusBadRequest, "the length of ding agent_id cannot be greater than 256")
}
conf.RecvUser = o.validAppArrayParam(conf.RecvUser, "ding recv_user", nil)
conf.RecvParty = o.validAppArrayParam(conf.RecvParty, "ding recv_party", nil)
}
func (o *AppController) validHttpAlarm(conf *models.HttpAlarmConf) {
if len(conf.RecvAddr) == 0 {
o.ServeError(http.StatusBadRequest, "the http recv_addr cannot be empty")
}
if len(conf.RecvAddr) > 128 {
o.ServeError(http.StatusBadRequest, "the count of http recv_addr cannot be greater than 128")
}
conf.RecvAddr = o.validAppArrayParam(conf.RecvAddr, "http recv_addr", nil)
}
// @router /delete [post]
func (o *AppController) Delete() {
var app = &models.App{}
o.UnmarshalJson(app)
if app.Id == "" {
o.ServeError(http.StatusBadRequest, "the id cannot be empty")
}
mutex.Lock()
defer mutex.Unlock()
count, err := models.GetAppCount()
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to get app count", err)
}
if count <= 1 {
o.ServeError(http.StatusBadRequest, "failed to remove app: keep at least one app")
}
app, err = models.RemoveAppById(app.Id)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to remove app", err)
}
err = models.RemoveRaspByAppId(app.Id)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to remove rasp by app_id", err)
}
err = models.RemovePluginByAppId(app.Id)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to remove plugin by app_id", err)
}
models.AddOperation(app.Id, models.OperationTypeDeleteApp, o.Ctx.Input.IP(), "Deleted app with name "+app.Name)
o.ServeWithEmptyData()
}
func (o *AppController) validAppArrayParam(param []string, paramName string,
valid func(interface{}, string) *validation.Result) []string {
if param != nil {
for i, v := range param {
if len(v) > 256 {
o.ServeError(http.StatusBadRequest,
"the element's length of "+paramName+" cannot be greater than 256")
}
if valid != nil {
if result := valid(v, "valid"); !result.Ok {
o.ServeError(http.StatusBadRequest,
"the "+strconv.Itoa(i)+"th element's format of "+paramName+" is error: "+result.Error.Message)
}
}
}
} else {
param = make([]string, 0)
}
return param
}
func (o *AppController) validateAppConfig(config map[string]interface{}) {
for key, value := range config {
if value == nil {
o.ServeError(http.StatusBadRequest, "the value of "+key+" config cannot be nil")
}
if key == "" {
o.ServeError(http.StatusBadRequest,
"the config key can not be empty")
}
if len(key) > 512 {
o.ServeError(http.StatusBadRequest,
"the length of config key '"+key+"' must be less than 512")
}
if v, ok := value.(string); ok {
if len(v) >= 2048 {
o.ServeError(http.StatusBadRequest,
"the value's length of config key '"+key+"' must be less than 2048")
}
}
}
}
func (o *AppController) validateWhiteListConfig(config []models.WhitelistConfigItem) {
if len(config) > 200 {
o.ServeError(http.StatusBadRequest,
"the count of whitelist config items must be between (0,200]")
}
for _, value := range config {
if len(value.Url) > 200 || len(value.Url) == 0 {
o.ServeError(http.StatusBadRequest,
"the length of whitelist config url must be between [1,200]")
}
for key := range value.Hook {
if len(key) > 128 {
o.ServeError(http.StatusBadRequest,
"the length of hook's type can not be greater 128")
}
}
}
}
// @router /alarm/config [post]
func (o *AppController) ConfigAlarm() {
var param struct {
AppId string `json:"app_id"`
EmailAlarmConf *models.EmailAlarmConf `json:"email_alarm_conf,omitempty"`
DingAlarmConf *models.DingAlarmConf `json:"ding_alarm_conf,omitempty"`
HttpAlarmConf *models.HttpAlarmConf `json:"http_alarm_conf,omitempty"`
}
o.UnmarshalJson(¶m)
if param.AppId == "" {
o.ServeError(http.StatusBadRequest, "app_id can not be empty")
}
app, err := models.GetAppByIdWithoutMask(param.AppId)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to get app", err)
}
var updateData bson.M
if param.EmailAlarmConf != nil {
if param.EmailAlarmConf.Password == models.SecreteMask {
param.EmailAlarmConf.Password = app.EmailAlarmConf.Password
}
o.validEmailConf(param.EmailAlarmConf)
}
if param.HttpAlarmConf != nil {
o.validHttpAlarm(param.HttpAlarmConf)
}
if param.DingAlarmConf != nil {
if param.DingAlarmConf.CorpSecret == models.SecreteMask {
param.DingAlarmConf.CorpSecret = app.DingAlarmConf.CorpSecret
}
o.validDingConf(param.DingAlarmConf)
}
content, err := json.Marshal(param)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to encode param to json", err)
}
err = json.Unmarshal(content, &updateData)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to decode param json", err)
}
app, err = models.UpdateAppById(param.AppId, updateData)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to update alarm config", err)
}
models.AddOperation(app.Id, models.OperationTypeUpdateAlarmConfig, o.Ctx.Input.IP(),
"Alarm configuration updated for "+param.AppId)
o.Serve(app)
}
// @router /plugin/get [post]
func (o *AppController) GetPlugins() {
var param pageParam
o.UnmarshalJson(¶m)
o.ValidPage(param.Page, param.Perpage)
app, err := models.GetAppById(param.AppId)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to get app", err)
}
if app == nil {
o.ServeError(http.StatusBadRequest, "the app doesn't exist")
}
var result = make(map[string]interface{})
total, plugins, err := models.GetPluginsByApp(param.AppId, (param.Page-1)*param.Perpage, param.Perpage)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to get plugins", err)
}
result["total"] = total
result["total_page"] = math.Ceil(float64(total) / float64(param.Perpage))
result["page"] = param.Page
result["perpage"] = param.Perpage
result["data"] = plugins
o.Serve(result)
}
// @router /plugin/select/get [post]
func (o *AppController) GetSelectedPlugin() {
var param map[string]string
o.UnmarshalJson(¶m)
appId := param["app_id"]
if appId == "" {
o.ServeError(http.StatusBadRequest, "app_id cannot be empty")
}
plugin, err := models.GetSelectedPlugin(appId, false)
if err != nil {
if mgo.ErrNotFound == err {
o.ServeWithEmptyData()
return
}
o.ServeError(http.StatusBadRequest, "failed to get selected plugin", err)
}
o.Serve(plugin)
}
// @router /plugin/select [post]
func (o *AppController) SetSelectedPlugin() {
var param map[string]string
o.UnmarshalJson(¶m)
appId := param["app_id"]
if appId == "" {
o.ServeError(http.StatusBadRequest, "app_id cannot be empty")
}
pluginId := param["plugin_id"]
if pluginId == "" {
o.ServeError(http.StatusBadRequest, "plugin_id cannot be empty")
}
plugin, err := models.SetSelectedPlugin(appId, pluginId)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to set selected plugin", err)
}
models.AddOperation(appId, models.OperationTypeSetSelectedPlugin, o.Ctx.Input.IP(),
"Deployed plugin "+plugin.Name+": "+plugin.Version+" ["+plugin.Id+"]")
o.ServeWithEmptyData()
}
// @router /email/test [post]
func (o *AppController) TestEmail() {
var param map[string]string
o.UnmarshalJson(¶m)
appId := param["app_id"]
if appId == "" {
o.ServeError(http.StatusBadRequest, "app_id cannot be empty")
}
app, err := models.GetAppByIdWithoutMask(appId)
if err != nil {
o.ServeError(http.StatusBadRequest, "can not find the app", err)
}
if !app.EmailAlarmConf.Enable {
o.ServeError(http.StatusBadRequest, "please enable the email alarm first")
}
err = models.PushEmailAttackAlarm(app, 0, nil, true)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to test email alarm", err)
}
o.ServeWithEmptyData()
}
// @router /ding/test [post]
func (o *AppController) TestDing(config map[string]interface{}) {
var param map[string]string
o.UnmarshalJson(¶m)
appId := param["app_id"]
if appId == "" {
o.ServeError(http.StatusBadRequest, "app_id cannot be empty")
}
app, err := models.GetAppByIdWithoutMask(appId)
if err != nil {
o.ServeError(http.StatusBadRequest, "can not find the app", err)
}
if !app.DingAlarmConf.Enable {
o.ServeError(http.StatusBadRequest, "please enable the ding ding alarm first")
}
err = models.PushDingAttackAlarm(app, 0, nil, true)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to test ding ding alarm", err)
}
o.ServeWithEmptyData()
}
// @router /http/test [post]
func (o *AppController) TestHttp(config map[string]interface{}) {
var param map[string]string
o.UnmarshalJson(¶m)
appId := param["app_id"]
if appId == "" {
o.ServeError(http.StatusBadRequest, "app_id cannot be empty")
}
app, err := models.GetAppByIdWithoutMask(appId)
if err != nil {
o.ServeError(http.StatusBadRequest, "can not find the app", err)
}
if !app.HttpAlarmConf.Enable {
o.ServeError(http.StatusBadRequest, "please enable the http alarm first")
}
err = models.PushHttpAttackAlarm(app, 0, nil, true)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to test http alarm", err)
}
o.ServeWithEmptyData()
}
//Copyright 2017-2019 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package fore_logs
import (
"rasp-cloud/controllers"
"encoding/json"
"net/http"
"rasp-cloud/models"
"rasp-cloud/models/logs"
"math"
"time"
)
// Operations about attack alarm message
type AttackAlarmController struct {
controllers.BaseController
}
// @router /aggr/time [post]
func (o *AttackAlarmController) AggregationWithTime() {
var param = &logs.AggrTimeParam{}
o.UnmarshalJson(¶m)
if param.AppId != "" {
_, err := models.GetAppById(param.AppId)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to get the app: "+param.AppId)
}
} else {
param.AppId = "*"
}
if param.StartTime <= 0 {
o.ServeError(http.StatusBadRequest, "start_time must be greater than 0")
}
if param.EndTime <= 0 {
o.ServeError(http.StatusBadRequest, "end_time must be greater than 0")
}
if param.StartTime > param.EndTime {
o.ServeError(http.StatusBadRequest, "start_time cannot be greater than end_time")
}
duration := time.Duration(param.EndTime-param.StartTime) * time.Millisecond
if duration > 366*24*time.Hour {
o.ServeError(http.StatusBadRequest, "time duration can not be greater than 366 days")
}
if param.Interval == "" {
o.ServeError(http.StatusBadRequest, "interval cannot be empty")
}
if param.TimeZone == "" {
o.ServeError(http.StatusBadRequest, "time_zone cannot be empty")
}
if len(param.Interval) > 32 {
o.ServeError(http.StatusBadRequest, "the length of interval cannot be greater than 32")
}
if len(param.TimeZone) > 32 {
o.ServeError(http.StatusBadRequest, "the length of time_zone cannot be greater than 32")
}
result, err :=
logs.AggregationAttackWithTime(param.StartTime, param.EndTime, param.Interval, param.TimeZone, param.AppId)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to get aggregation from es", err)
}
o.Serve(result)
}
// @router /aggr/type [post]
func (o *AttackAlarmController) AggregationWithType() {
var param = &logs.AggrFieldParam{}
o.UnmarshalJson(¶m)
o.validFieldAggrParam(param)
result, err :=
logs.AggregationAttackWithType(param.StartTime, param.EndTime, param.Size, param.AppId)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to get aggregation from es", err)
}
o.Serve(result)
}
// @router /aggr/ua [post]
func (o *AttackAlarmController) AggregationWithUserAgent() {
var param = &logs.AggrFieldParam{}
o.UnmarshalJson(¶m)
o.validFieldAggrParam(param)
result, err :=
logs.AggregationAttackWithUserAgent(param.StartTime, param.EndTime, param.Size, param.AppId)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to get aggregation from es", err)
}
o.Serve(result)
}
// @router /search [post]
func (o *AttackAlarmController) Search() {
param, searchData := o.handleAttackSearchParam()
total, result, err := logs.SearchLogs(param.Data.StartTime, param.Data.EndTime,
false, searchData, "event_time", param.Page,
param.Perpage, false, logs.AttackAlarmInfo.EsAliasIndex+"-"+param.Data.AppId)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to search data from es", err)
}
o.Serve(map[string]interface{}{
"total": total,
"total_page": math.Ceil(float64(total) / float64(param.Perpage)),
"page": param.Page,
"perpage": param.Perpage,
"data": result,
})
}
// @router /aggr/vuln [post]
func (o *AttackAlarmController) AggregationVuln() {
param, searchData := o.handleAttackSearchParam()
total, result, err := logs.SearchLogs(param.Data.StartTime, param.Data.EndTime,
true, searchData, "event_time", param.Page,
param.Perpage, false, logs.AttackAlarmInfo.EsAliasIndex+"-"+param.Data.AppId)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to search data from es", err)
}
o.Serve(map[string]interface{}{
"total": total,
"total_page": math.Ceil(float64(total) / float64(param.Perpage)),
"page": param.Page,
"perpage": param.Perpage,
"data": result,
})
}
func (o *AttackAlarmController) handleAttackSearchParam() (param *logs.SearchAttackParam,
searchData map[string]interface{}) {
param = &logs.SearchAttackParam{}
o.UnmarshalJson(¶m)
if param.Data == nil {
o.ServeError(http.StatusBadRequest, "search data can not be empty")
}
if param.Data.AppId != "" {
_, err := models.GetAppById(param.Data.AppId)
if err != nil {
o.ServeError(http.StatusBadRequest, "cannot get the app: "+param.Data.AppId, err)
}
} else {
param.Data.AppId = "*"
}
if param.Data.StartTime <= 0 {
o.ServeError(http.StatusBadRequest, "start_time must be greater than 0")
}
if param.Data.EndTime <= 0 {
o.ServeError(http.StatusBadRequest, "end_time must be greater than 0")
}
if param.Data.StartTime > param.Data.EndTime {
o.ServeError(http.StatusBadRequest, "start_time cannot be greater than end_time")
}
o.ValidPage(param.Page, param.Perpage)
content, err := json.Marshal(param.Data)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to encode search data", err)
}
err = json.Unmarshal(content, &searchData)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to decode search data", err)
}
delete(searchData, "start_time")
delete(searchData, "end_time")
delete(searchData, "app_id")
return
}
func (o *AttackAlarmController) validFieldAggrParam(param *logs.AggrFieldParam) {
if param.AppId != "" {
_, err := models.GetAppById(param.AppId)
if err != nil {
o.ServeError(http.StatusBadRequest, "cannot get the app: "+param.AppId, err)
}
} else {
param.AppId = "*"
}
if param.StartTime <= 0 {
o.ServeError(http.StatusBadRequest, "start_time must be greater than 0")
}
if param.EndTime <= 0 {
o.ServeError(http.StatusBadRequest, "end_time must be greater than 0")
}
if param.StartTime > param.EndTime {
o.ServeError(http.StatusBadRequest, "start_time cannot be greater than end_time")
}
duration := time.Duration(param.EndTime-param.StartTime) * time.Millisecond
if duration > 366*24*time.Hour {
o.ServeError(http.StatusBadRequest, "time duration can not be greater than 366 days")
}
if param.Size <= 0 {
o.ServeError(http.StatusBadRequest, "size must be greater than 0")
}
}
//Copyright 2017-2018 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package fore_logs
import (
"rasp-cloud/controllers"
"encoding/json"
"net/http"
"rasp-cloud/models"
"rasp-cloud/models/logs"
"math"
)
type ErrorController struct {
controllers.BaseController
}
// @router /search [post]
func (o *ErrorController) Search() {
var param = &logs.SearchErrorParam{}
o.UnmarshalJson(¶m)
if param.Data == nil {
o.ServeError(http.StatusBadRequest, "search data can not be empty")
}
if param.Data.AppId != "" {
_, err := models.GetAppById(param.Data.AppId)
if err != nil {
o.ServeError(http.StatusBadRequest, "cannot get the app: "+param.Data.AppId, err)
}
} else {
param.Data.AppId = "*"
}
o.ValidPage(param.Page, param.Perpage)
if param.Data.StartTime <= 0 {
o.ServeError(http.StatusBadRequest, "start_time must be greater than 0")
}
if param.Data.EndTime <= 0 {
o.ServeError(http.StatusBadRequest, "end_time must be greater than 0")
}
if param.Data.StartTime > param.Data.EndTime {
o.ServeError(http.StatusBadRequest, "start_time cannot be greater than end_time")
}
content, err := json.Marshal(param.Data)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to encode search data", err)
}
var searchData map[string]interface{}
err = json.Unmarshal(content, &searchData)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to decode search data", err)
}
delete(searchData, "start_time")
delete(searchData, "end_time")
delete(searchData, "app_id")
total, result, err := logs.SearchLogs(param.Data.StartTime, param.Data.EndTime, false, searchData, "event_time",
param.Page, param.Perpage, false, logs.ErrorAlarmInfo.EsAliasIndex+"-"+param.Data.AppId)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to search data from es", err)
}
o.Serve(map[string]interface{}{
"total": total,
"total_page": math.Ceil(float64(total) / float64(param.Perpage)),
"page": param.Page,
"perpage": param.Perpage,
"data": result,
})
}
//Copyright 2017-2019 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package fore_logs
import (
"rasp-cloud/controllers"
"rasp-cloud/models/logs"
"encoding/json"
"rasp-cloud/models"
"net/http"
"math"
)
// Operations about policy alarm message
type PolicyAlarmController struct {
controllers.BaseController
}
// @router /search [post]
func (o *PolicyAlarmController) Search() {
var param = &logs.SearchPolicyParam{}
o.UnmarshalJson(¶m)
if param.Data == nil {
o.ServeError(http.StatusBadRequest, "search data can not be empty")
}
if param.Data.AppId != "" {
_, err := models.GetAppById(param.Data.AppId)
if err != nil {
o.ServeError(http.StatusBadRequest, "cannot get the app: "+param.Data.AppId, err)
}
} else {
param.Data.AppId = "*"
}
if param.Data.StartTime <= 0 {
o.ServeError(http.StatusBadRequest, "start_time must be greater than 0")
}
if param.Data.EndTime <= 0 {
o.ServeError(http.StatusBadRequest, "end_time must be greater than 0")
}
if param.Data.StartTime > param.Data.EndTime {
o.ServeError(http.StatusBadRequest, "start_time cannot be greater than end_time")
}
o.ValidPage(param.Page, param.Perpage)
content, err := json.Marshal(param.Data)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to encode search data", err)
}
var searchData map[string]interface{}
err = json.Unmarshal(content, &searchData)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to decode search data", err)
}
delete(searchData, "start_time")
delete(searchData, "end_time")
delete(searchData, "app_id")
total, result, err := logs.SearchLogs(param.Data.StartTime, param.Data.EndTime, false, searchData, "event_time",
param.Page, param.Perpage, false, logs.PolicyAlarmInfo.EsAliasIndex+"-"+param.Data.AppId)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to search data from es", err)
}
o.Serve(map[string]interface{}{
"total": total,
"total_page": math.Ceil(float64(total) / float64(param.Perpage)),
"page": param.Page,
"perpage": param.Perpage,
"data": result,
})
}
//Copyright 2017-2019 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package api
import (
"math"
"net/http"
"rasp-cloud/controllers"
"rasp-cloud/models"
)
type OperationController struct {
controllers.BaseController
}
// @router /search [post]
func (o *OperationController) Search() {
var param struct {
Data *models.Operation `json:"data"`
StartTime int64 `json:"start_time"`
EndTime int64 `json:"end_time"`
Page int `json:"page"`
Perpage int `json:"perpage"`
}
o.UnmarshalJson(¶m)
o.ValidPage(param.Page, param.Perpage)
if param.Data == nil {
o.ServeError(http.StatusBadRequest, "search data can not be empty")
}
if param.StartTime <= 0 {
o.ServeError(http.StatusBadRequest, "start_time must be greater than 0")
}
if param.EndTime <= 0 {
o.ServeError(http.StatusBadRequest, "end_time must be greater than 0")
}
if param.StartTime > param.EndTime {
o.ServeError(http.StatusBadRequest, "start_time cannot be greater than end_time")
}
var result = make(map[string]interface{})
total, operations, err := models.FindOperation(param.Data, param.StartTime, param.EndTime, param.Page, param.Perpage)
if err != nil {
o.ServeError(http.StatusBadRequest, "Failed to get plugin list", err)
}
result["total"] = total
result["total_page"] = math.Ceil(float64(total) / float64(param.Perpage))
result["page"] = param.Page
result["perpage"] = param.Perpage
result["data"] = operations
o.Serve(result)
}
//Copyright 2017-2019 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package api
import (
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
"io/ioutil"
"net/http"
"rasp-cloud/controllers"
"rasp-cloud/models"
"rasp-cloud/mongo"
)
// Operations about plugin
type PluginController struct {
controllers.BaseController
}
// @router / [post]
func (o *PluginController) Upload() {
appId := o.GetString("app_id")
if appId == "" {
o.ServeError(http.StatusBadRequest, "app_id can not be empty")
}
_, err := models.GetAppById(appId)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to get app", err)
}
uploadFile, info, err := o.GetFile("plugin")
if err != nil {
o.ServeError(http.StatusBadRequest, "parse uploadFile error", err)
}
if uploadFile == nil {
o.ServeError(http.StatusBadRequest, "must have the plugin parameter")
}
defer uploadFile.Close()
if info.Size == 0 {
o.ServeError(http.StatusBadRequest, "the upload file cannot be empty")
}
pluginContent, err := ioutil.ReadAll(uploadFile)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to read upload plugin", err)
}
latestPlugin, err := models.AddPlugin(pluginContent, appId)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to add plugin", err)
}
models.AddOperation(appId, models.OperationTypeUploadPlugin, o.Ctx.Input.IP(),
"New plugin uploaded: "+latestPlugin.Id)
o.Serve(latestPlugin)
}
// @router /get [post]
func (o *PluginController) Get() {
var param map[string]string
o.UnmarshalJson(¶m)
pluginId := param["id"]
if pluginId == "" {
o.ServeError(http.StatusBadRequest, "plugin_id cannot be empty")
}
plugin, err := models.GetPluginById(pluginId, false)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to get plugin", err)
}
o.Serve(plugin)
}
// @router /download [get]
func (o *PluginController) Download() {
pluginId := o.GetString("id")
plugin, err := models.GetPluginById(pluginId, true)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to get plugin", err)
}
o.Ctx.Output.Header("Content-Type", "text/plain")
if plugin.Name == "" {
plugin.Name = "plugin"
}
o.Ctx.Output.Header("Content-Disposition", "attachment;filename="+plugin.Name+"-"+plugin.Version+".js")
if len(plugin.OriginContent) != 0 {
o.Ctx.Output.Body([]byte(plugin.OriginContent))
} else {
o.Ctx.Output.Body([]byte(plugin.Content))
}
}
// @router /algorithm/config [post]
func (o *PluginController) UpdateAppAlgorithmConfig() {
var param struct {
PluginId string `json:"id"`
Config map[string]interface{} `json:"config"`
}
o.UnmarshalJson(¶m)
if param.PluginId == "" {
o.ServeError(http.StatusBadRequest, "plugin id can not be empty")
}
if param.Config == nil {
o.ServeError(http.StatusBadRequest, "config can not be empty")
}
appId, err := models.UpdateAlgorithmConfig(param.PluginId, param.Config)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to update algorithm config", err)
}
models.AddOperation(appId, models.OperationTypeUpdateAlgorithmConfig,
o.Ctx.Input.IP(), "Algorithm config updated for plugin: "+param.PluginId)
o.ServeWithEmptyData()
}
// @router /algorithm/restore [post]
func (o *PluginController) RestoreAlgorithmConfig() {
var param map[string]string
o.UnmarshalJson(¶m)
pluginId := param["id"]
if pluginId == "" {
o.ServeError(http.StatusBadRequest, "plugin_id cannot be empty")
}
appId, err := models.RestoreDefaultConfiguration(pluginId)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to restore the default algorithm config", err)
}
models.AddOperation(appId, models.OperationTypeRestorePlugin, o.Ctx.Input.IP(),
"Restored algorithm config for plugin: "+pluginId)
o.ServeWithEmptyData()
}
// @router /delete [post]
func (o *PluginController) Delete() {
var param map[string]string
o.UnmarshalJson(¶m)
pluginId := param["id"]
if pluginId == "" {
o.ServeError(http.StatusBadRequest, "plugin_id cannot be empty")
}
plugin, err := models.GetPluginById(pluginId, false)
if err != nil {
o.ServeError(http.StatusBadRequest, "can not get the plugin", err)
}
var app *models.App
err = mongo.FindOne("app", bson.M{"selected_plugin_id": pluginId}, &app)
if err != nil && err != mgo.ErrNotFound {
o.ServeError(http.StatusBadRequest, "failed to get app", err)
}
if app != nil {
o.ServeError(http.StatusBadRequest, "Unable to delete a plugin in use. Plugin is used by appid: "+app.Id)
}
err = models.DeletePlugin(pluginId)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to delete the plugin", err)
}
models.AddOperation(plugin.AppId, models.OperationTypeDeletePlugin, o.Ctx.Input.IP(),
"Deleted plugin: "+plugin.Id)
o.ServeWithEmptyData()
}
//Copyright 2017-2019 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package api
import (
"math"
"net/http"
"rasp-cloud/controllers"
"rasp-cloud/models"
"github.com/astaxie/beego/validation"
)
type RaspController struct {
controllers.BaseController
}
// @router /search [post]
func (o *RaspController) Search() {
var param struct {
Data *models.Rasp `json:"data" `
Page int `json:"page"`
Perpage int `json:"perpage"`
}
o.UnmarshalJson(¶m)
if param.Data == nil {
o.ServeError(http.StatusBadRequest, "search data can not be empty")
}
o.ValidPage(param.Page, param.Perpage)
total, rasps, err := models.FindRasp(param.Data, param.Page, param.Perpage)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to get rasp", err)
}
if rasps == nil {
rasps = make([]*models.Rasp, 0)
}
var result = make(map[string]interface{})
result["total"] = total
result["total_page"] = math.Ceil(float64(total) / float64(param.Perpage))
result["page"] = param.Page
result["perpage"] = param.Perpage
result["data"] = rasps
o.Serve(result)
}
// @router /delete [post]
func (o *RaspController) Delete() {
var rasp struct {
Id string `json:"id"`
AppId string `json:"app_id"`
RegisterIp string `json:"register_ip"`
ExpiredTime int64 `json:"expire_time"`
}
o.UnmarshalJson(&rasp)
if rasp.AppId == "" {
o.ServeError(http.StatusBadRequest, "the app_id can not be empty")
}
if rasp.Id != "" {
err := models.RemoveRaspById(rasp.Id)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to remove rasp by id", err)
}
models.AddOperation(rasp.AppId, models.OperationTypeDeleteRasp, o.Ctx.Input.IP(),
"Deleted RASP agent by id: "+rasp.Id)
o.Serve(map[string]interface{}{
"count": 1,
})
} else {
selector := make(map[string]interface{})
if rasp.ExpiredTime == 0 && rasp.RegisterIp == "" {
o.ServeError(http.StatusBadRequest,
"expire_time and register ip can not be empty at the same time")
}
if rasp.RegisterIp != "" {
selector["register_ip"] = rasp.RegisterIp
valid := validation.Validation{}
if result := valid.IP(rasp.RegisterIp, "IP"); !result.Ok {
o.ServeError(http.StatusBadRequest, "rasp register_ip format error"+result.Error.Message)
}
}
if rasp.ExpiredTime != 0 {
selector["expire_time"] = rasp.ExpiredTime
if rasp.ExpiredTime < 0 {
o.ServeError(http.StatusBadRequest, "expire_time must be greater than 0")
}
}
removedCount, err := models.RemoveRaspBySelector(selector, rasp.AppId)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to remove rasp by register ip", err)
}
models.AddOperation(rasp.AppId, models.OperationTypeDeleteRasp, o.Ctx.Input.IP(),
"Deleted RASP agent by register ip: "+rasp.RegisterIp)
o.Serve(map[string]interface{}{
"count": removedCount,
})
}
}
//Copyright 2017-2019 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package api
import (
"fmt"
"net/http"
"rasp-cloud/controllers"
"rasp-cloud/models"
)
type ReportController struct {
controllers.BaseController
}
var (
intervals = [...]string{"hour", "day", "month"}
)
// @router /dashboard [post]
func (o *ReportController) Search() {
var query map[string]interface{}
o.UnmarshalJson(&query)
startTimeParam := query["start_time"]
if startTimeParam == nil {
o.ServeError(http.StatusBadRequest, "start_time cannot be empty")
}
startTime, ok := startTimeParam.(float64)
if !ok {
o.ServeError(http.StatusBadRequest, "start_time must be number")
}
if startTime <= 0 {
o.ServeError(http.StatusBadRequest, "start_time must be greater than 0")
}
endTimeParam := query["end_time"]
if endTimeParam == nil {
o.ServeError(http.StatusBadRequest, "end_time cannot be empty")
}
endTime, ok := endTimeParam.(float64)
if !ok {
o.ServeError(http.StatusBadRequest, "end_time must be number")
}
if endTime <= 0 {
o.ServeError(http.StatusBadRequest, "end_time must be greater than 0")
}
intervalParam := query["interval"]
if intervalParam == nil {
o.ServeError(http.StatusBadRequest, "interval cannot be empty")
}
interval, ok := intervalParam.(string)
if !ok {
o.ServeError(http.StatusBadRequest, "interval must be string")
}
timeZoneParam := query["time_zone"]
if timeZoneParam == nil {
o.ServeError(http.StatusBadRequest, "time_zone cannot be empty")
}
timeZone, ok := timeZoneParam.(string)
if !ok {
o.ServeError(http.StatusBadRequest, "time_zone must be string")
}
isValidInterval := false
for index := range intervals {
if interval == intervals[index] {
isValidInterval = true
}
}
if !isValidInterval {
o.ServeError(http.StatusBadRequest, "the interval must be in"+fmt.Sprintf("%v", intervals))
}
appIdParam := query["app_id"]
appId := "*"
if appIdParam != nil {
appId, ok = appIdParam.(string)
if !ok {
o.ServeError(http.StatusBadRequest, "app_id must be string")
}
_, err := models.GetAppById(appId)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to get app", err)
}
}
err, result := models.GetHistoryRequestSum(int64(startTime), int64(endTime), interval, timeZone, appId)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to get request sum form ES", err)
}
o.Serve(result)
}
//Copyright 2017-2019 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package api
import (
"rasp-cloud/controllers"
"rasp-cloud/models"
"net/http"
"gopkg.in/mgo.v2"
"strings"
)
type ServerController struct {
controllers.BaseController
}
// @router /url/get [post]
func (o *ServerController) GetUrl() {
serverUrl, err := models.GetServerUrl()
if err != nil {
if mgo.ErrNotFound == err {
o.Serve(models.ServerUrl{AgentUrls: []string{}})
return
}
o.ServeError(http.StatusBadRequest, "failed to get serverUrl", err)
}
o.Serve(serverUrl)
}
// @router /url [post]
func (o *ServerController) PutUrl() {
var serverUrl = &models.ServerUrl{}
o.UnmarshalJson(&serverUrl)
if !validHttpUrl(serverUrl.PanelUrl) {
o.ServeError(http.StatusBadRequest, "Invalid panel url: "+serverUrl.PanelUrl)
}
if len(serverUrl.PanelUrl) > 512 {
o.ServeError(http.StatusBadRequest, "the length of panel url cannot be greater than 512")
}
if len(serverUrl.AgentUrls) > 1024 {
o.ServeError(http.StatusBadRequest, "the count of agent url cannot be greater than 1024")
}
if serverUrl.AgentUrls != nil {
for _, agentUrl := range serverUrl.AgentUrls {
if len(agentUrl) > 512 {
o.ServeError(http.StatusBadRequest, "the length of agent url cannot be greater than 512")
}
if !validHttpUrl(agentUrl) {
o.ServeError(http.StatusBadRequest, "Invalid agent url: "+agentUrl)
}
}
}
err := models.PutServerUrl(serverUrl)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to put server url", err)
}
o.Serve(serverUrl)
}
func validHttpUrl(url string) bool {
return strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://")
}
//Copyright 2017-2019 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package api
import (
"math"
"net/http"
"rasp-cloud/controllers"
"rasp-cloud/models"
)
type TokenController struct {
controllers.BaseController
}
// @router /get [post]
func (o *TokenController) Get() {
var param map[string]int
o.UnmarshalJson(¶m)
page := param["page"]
perpage := param["perpage"]
o.ValidPage(page, perpage)
total, tokens, err := models.GetAllToken(page, perpage)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to get tokens", err)
}
if tokens == nil {
tokens = make([]*models.Token, 0)
}
var result = make(map[string]interface{})
result["total"] = total
result["total_page"] = math.Ceil(float64(total) / float64(perpage))
result["page"] = page
result["perpage"] = perpage
result["data"] = tokens
o.Serve(result)
}
// @router / [post]
func (o *TokenController) Post() {
var token *models.Token
o.UnmarshalJson(&token)
if len(token.Description) > 1024 {
o.ServeError(http.StatusBadRequest, "the length of the token description must be less than 1024")
}
token, err := models.AddToken(token)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to create new token", err)
}
o.Serve(token)
}
// @router /delete [post]
func (o *TokenController) Delete() {
var token *models.Token
o.UnmarshalJson(&token)
if len(token.Token) == 0 {
o.ServeError(http.StatusBadRequest, "the token param cannot be empty")
}
currentToken := o.Ctx.Input.Header(models.AuthTokenName)
if currentToken == token.Token {
o.ServeError(http.StatusBadRequest, "can not delete the token currently in use")
}
token, err := models.RemoveToken(token.Token)
if err != nil {
o.ServeError(http.StatusBadRequest, "failed to remove token", err)
}
o.Serve(token)
}
//Copyright 2017-2019 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package api
import (
"crypto/md5"
"fmt"
"math/rand"
"net/http"
"rasp-cloud/controllers"
"rasp-cloud/models"
"strconv"
"time"
)
type UserController struct {
controllers.BaseController
}
// @router /login [post]
func (o *UserController) Login() {
var loginData map[string]string
o.UnmarshalJson(&loginData)
logUser := loginData["username"]
logPasswd := loginData["password"]
if logUser == "" || logPasswd == "" {
o.ServeError(http.StatusBadRequest, "username or password cannot be empty")
}
if len(logUser) > 512 || len(logPasswd) > 512 {
o.ServeError(http.StatusBadRequest, "the length of username or password cannot be greater than 512")
}
err := models.VerifyUser(logUser, logPasswd)
if err != nil {
o.ServeError(http.StatusBadRequest, "username or password is incorrect", err)
}
cookie := fmt.Sprintf("%x", md5.Sum([]byte(strconv.Itoa(rand.Intn(10000)) + logUser + "openrasp"+
strconv.FormatInt(time.Now().UnixNano(), 10))))
err = models.NewCookie(cookie)
if err != nil {
o.ServeError(http.StatusUnauthorized, "failed to create cookie", err)
}
o.Ctx.SetCookie(models.AuthCookieName, cookie)
o.ServeWithEmptyData()
}
// @router /islogin [get,post]
func (o *UserController) IsLogin() {
o.ServeWithEmptyData()
}
// @router /update [post]
func (o *UserController) Update() {
var param struct {
OldPwd string `json:"old_password"`
NewPwd string `json:"new_password"`
}
o.UnmarshalJson(¶m)
if param.OldPwd == "" {
o.ServeError(http.StatusBadRequest, "old_password can not be empty")
}
if param.NewPwd == "" {
o.ServeError(http.StatusBadRequest, "new_password can not be empty")
}
err := models.RemoveAllCookie()
if err != nil {
o.ServeError(http.StatusBadRequest, err.Error())
}
err = models.UpdatePassword(param.OldPwd, param.NewPwd)
if err != nil {
o.ServeError(http.StatusBadRequest, err.Error())
}
o.ServeWithEmptyData()
}
// @router /logout [get,post]
func (o *UserController) Logout() {
o.Ctx.SetCookie(models.AuthCookieName, "")
cookie := o.Ctx.GetCookie(models.AuthCookieName)
models.RemoveCookie(cookie)
o.ServeWithEmptyData()
}
//Copyright 2017-2019 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package controllers
import (
"github.com/astaxie/beego"
"net/http"
"encoding/json"
)
// base controller
type BaseController struct {
beego.Controller
}
func (o *BaseController) Serve(data interface{}) {
o.Data["json"] = map[string]interface{}{"status": 0, "description": "ok", "data": data}
o.ServeJSON()
}
func (o *BaseController) ServeWithEmptyData() {
o.Data["json"] = map[string]interface{}{"status": 0, "description": "ok", "data": make(map[string]interface{})}
o.ServeJSON()
}
func (o *BaseController) ServeError(code int, description string, err ...error) {
if len(err) > 0 && err[0] != nil {
description = description + ": " + err[0].Error()
}
o.ServeStatusCode(code, description)
panic(description)
}
func (o *BaseController) ServeStatusCode(code int, description ...string) {
var des string
if len(description) == 0 {
des = http.StatusText(code)
} else {
des = description[0]
}
o.Data["json"] = map[string]interface{}{"status": code, "description": des}
o.ServeJSON()
}
func (o *BaseController) UnmarshalJson(v interface{}) {
err := json.Unmarshal(o.Ctx.Input.RequestBody, v)
if err != nil {
o.ServeError(http.StatusBadRequest, "Invalid JSON request", err)
}
}
func (o *BaseController) ValidPage(page int, perpage int) {
if page <= 0 {
o.ServeError(http.StatusBadRequest, "page must be greater than 0")
}
if perpage <= 0 {
o.ServeError(http.StatusBadRequest, "perpage must be greater than 0")
}
if perpage > 100 {
o.ServeError(http.StatusBadRequest, "perpage must be less than 100")
}
}
//Copyright 2017-2019 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package controllers
type ErrorController struct {
BaseController
}
func (o *ErrorController) Error404() {
o.ServeStatusCode(404)
}
func (o *ErrorController) Error500() {
o.ServeStatusCode(500)
}
func (o *ErrorController) Error503() {
o.ServeStatusCode(503)
}
func (o *ErrorController) Error502() {
o.ServeStatusCode(502)
}
package controllers
type PingController struct {
BaseController
}
// @router / [get,post]
func (o *PingController) Ping() {
o.ServeWithEmptyData()
}
//Copyright 2017-2019 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package environment
/*
const char* build_time(void)
{
static const char* psz_build_time = "["__DATE__ " " __TIME__ "]";
return psz_build_time;
}
*/
import "C"
import (
"flag"
"github.com/astaxie/beego"
"github.com/astaxie/beego/logs"
"log"
"os"
"os/exec"
"rasp-cloud/tools"
"rasp-cloud/conf"
"fmt"
"golang.org/x/crypto/ssh/terminal"
"syscall"
"bytes"
)
var (
Version = "1.1"
BuildTime = C.GoString(C.build_time())
)
func init() {
StartFlag := &conf.Flag{}
StartFlag.StartType = flag.String("type", "", "use to provide different routers")
StartFlag.Daemon = flag.Bool("d", false, "use to run as daemon process")
StartFlag.Version = flag.Bool("version", false, "use to get version")
flag.Parse()
if *StartFlag.Version {
fmt.Println(Version)
fmt.Println("Build Time: " + BuildTime)
os.Exit(0)
}
if *StartFlag.StartType == conf.StartTypeReset {
HandleReset(StartFlag)
}
if *StartFlag.Daemon {
HandleDaemon()
}
initLogger()
initEnvConf()
if *StartFlag.StartType == "" {
allType := conf.StartTypeDefault
StartFlag.StartType = &allType
}
conf.InitConfig(StartFlag)
beego.Info("===== start type: " + *StartFlag.StartType + " =====")
}
func HandleReset(startFlag *conf.Flag) {
fmt.Print("Enter new admin password: ")
pwd1, err := terminal.ReadPassword(int(syscall.Stdin))
fmt.Println()
if err != nil {
fmt.Println("failed to read password from terminal: " + err.Error())
os.Exit(tools.ErrCodeResetUserFailed)
}
fmt.Print("Retype new admin password: ")
pwd2, err := terminal.ReadPassword(int(syscall.Stdin))
fmt.Println()
if err != nil {
fmt.Println("failed to read password from terminal: " + err.Error())
os.Exit(tools.ErrCodeResetUserFailed)
}
if bytes.Compare(pwd1, pwd2) != 0 {
fmt.Println("Sorry, passwords do not match")
os.Exit(tools.ErrCodeResetUserFailed)
} else {
pwd := string(pwd1)
startFlag.Password = &pwd
}
}
func HandleDaemon() {
err := fork()
if err != nil {
tools.Panic(tools.ErrCodeInitChildProcessFailed, "failed to launch child process, error", err)
}
log.Println("start successfully, for details please check the log in 'logs/api/agent-cloud.log'")
os.Exit(0)
}
func fork() (err error) {
path := os.Args[0]
var args []string
if len(os.Args) > 1 {
for _, arg := range os.Args[1:] {
if arg == "-d" {
break
}
args = append(args, arg)
}
}
log.Println("args:", args)
cmd := exec.Command(path, args...)
err = cmd.Start()
return
}
func initLogger() {
logPath := tools.GetCurrentPathWithPanic() + "/logs/api"
if isExists, _ := tools.PathExists(logPath); !isExists {
err := os.MkdirAll(logPath, os.ModePerm)
if err != nil {
tools.Panic(tools.ErrCodeLogInitFailed, "failed to create logs/api dir", err)
}
}
logs.SetLogFuncCall(true)
logs.SetLogger(logs.AdapterFile,
`{"filename":"`+logPath+`/agent-cloud.log","daily":true,"maxdays":10,"perm":"0777"}`)
}
func initEnvConf() {
if beego.BConfig.RunMode == "dev" {
logs.SetLevel(beego.LevelDebug)
} else {
logs.SetLevel(beego.LevelInformational)
beego.BConfig.EnableErrorsShow = false
beego.BConfig.EnableErrorsRender = false
}
}
//Copyright 2017-2019 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package es
import (
"github.com/olivere/elastic"
"time"
"context"
"github.com/astaxie/beego/logs"
"strconv"
"github.com/astaxie/beego"
"rasp-cloud/tools"
"fmt"
"strings"
"rasp-cloud/conf"
)
var (
ElasticClient *elastic.Client
Version string
ttlIndexes = make(chan map[string]time.Duration, 1)
minEsVersion = "5.6.0"
)
func init() {
ttlIndexes <- make(map[string]time.Duration)
if *conf.AppConfig.Flag.StartType != conf.StartTypeReset {
esAddr := conf.AppConfig.EsAddr
client, err := elastic.NewSimpleClient(elastic.SetURL(esAddr),
elastic.SetBasicAuth(conf.AppConfig.EsUser, conf.AppConfig.EsPwd),
elastic.SetSnifferTimeoutStartup(5*time.Second),
elastic.SetSnifferTimeout(5*time.Second),
elastic.SetSnifferInterval(30*time.Minute))
if err != nil {
tools.Panic(tools.ErrCodeESInitFailed, "init ES failed", err)
}
go startTTL(24 * time.Hour)
Version, err = client.ElasticsearchVersion(esAddr)
if err != nil {
tools.Panic(tools.ErrCodeESInitFailed, "failed to get es version", err)
}
beego.Info("ES version: " + Version)
if strings.Compare(Version, minEsVersion) < 0 {
tools.Panic(tools.ErrCodeESInitFailed, "unable to support the ElasticSearch with a version lower than "+
minEsVersion+ ","+ " the current version is "+ Version, nil)
}
ElasticClient = client
}
}
func startTTL(duration time.Duration) {
ticker := time.NewTicker(duration)
for {
select {
case <-ticker.C:
DeleteExpiredData()
}
}
}
func DeleteExpiredData() {
defer func() {
if r := recover(); r != nil {
beego.Error(r)
}
}()
ttls := <-ttlIndexes
defer func() {
ttlIndexes <- ttls
}()
for index, duration := range ttls {
expiredTime := strconv.FormatInt((time.Now().UnixNano()-int64(duration))/1000000, 10)
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(10*time.Second))
r, err := ElasticClient.DeleteByQuery(index).QueryString("@timestamp:<" + expiredTime).Do(ctx)
if err != nil {
if r != nil && r.Failures != nil {
beego.Error(r.Failures)
}
beego.Error("failed to delete expired data for index " + index + ": " + err.Error())
} else {
var deleteNum int64
if r != nil {
deleteNum = r.Deleted
}
beego.Info("delete expired data successfully for index " + index + ", total: " +
strconv.FormatInt(deleteNum, 10))
}
cancel()
}
}
func RegisterTTL(duration time.Duration, index string) {
ttls := <-ttlIndexes
defer func() {
ttlIndexes <- ttls
}()
ttls[index] = duration
}
func CreateTemplate(name string, body string) error {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(15*time.Second))
defer cancel()
_, err := elastic.NewIndicesPutTemplateService(ElasticClient).Name(name).BodyString(body).Do(ctx)
if err != nil {
return err
}
beego.Info("put es template: " + name)
return nil
}
func CreateEsIndex(index string) error {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(15*time.Second))
defer cancel()
exists, err := ElasticClient.IndexExists(index).Do(ctx)
if err != nil {
return err
}
if !exists {
createResult, err := ElasticClient.CreateIndex(index).Do(ctx)
if err != nil {
return err
}
logs.Info("create es index: " + createResult.Index)
if err != nil {
beego.Error("failed to create index with name " + index + ": " + err.Error())
return err
}
}
return nil
}
func Insert(index string, docType string, doc interface{}) (err error) {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(10*time.Second))
defer cancel()
_, err = ElasticClient.Index().Index(index).Type(docType).BodyJson(doc).Do(ctx)
return
}
func BulkInsert(docType string, docs []map[string]interface{}) (err error) {
bulkService := ElasticClient.Bulk()
for _, doc := range docs {
if doc["app_id"] == nil {
beego.Error("failed to get app_id param from alarm: " + fmt.Sprintf("%+v", doc))
}
if appId, ok := doc["app_id"].(string); ok {
if docType == "policy-alarm" {
bulkService.Add(elastic.NewBulkUpdateRequest().
Index("real-openrasp-" + docType + "-" + appId).
Type(docType).
Id(fmt.Sprint(doc["upsert_id"])).
DocAsUpsert(true).
Doc(doc))
} else {
if appId, ok := doc["app_id"].(string); ok {
bulkService.Add(elastic.NewBulkIndexRequest().
Index("real-openrasp-" + docType + "-" + appId).
Type(docType).
OpType("index").
Doc(doc))
}
}
} else {
beego.Error("the type of alarm's app_id param is not string: " + fmt.Sprintf("%+v", doc))
}
}
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(15*time.Second))
defer cancel()
_, err = bulkService.Do(ctx)
return err
}
//Copyright 2017-2019 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package es
import (
"rasp-cloud/conf"
"rasp-cloud/tools"
)
var attackAlarmTemplate = `
{
"template":"openrasp-attack-alarm-*",
"aliases" : {
"real-{index}" : {}
},
"settings": {
"analysis": {
"normalizer": {
"lowercase_normalizer": {
"type": "custom",
"filter": ["lowercase"]
}
}
}
},
"mappings": {
"attack-alarm": {
"_all": {
"enabled": false
},
"properties": {
"@timestamp":{
"type":"date"
},
"request_method": {
"type": "keyword",
"ignore_above": 50
},
"target": {
"type": "keyword",
"ignore_above": 256
},
"server_ip": {
"type": "keyword",
"ignore_above": 256
},
"client_ip": {
"type": "keyword",
"ignore_above": 256
},
"referer": {
"type": "keyword",
"ignore_above": 256
},
"user_agent": {
"type": "keyword",
"ignore_above": 512
},
"attack_source": {
"type": "keyword",
"ignore_above": 256
},
"path": {
"type": "keyword",
"ignore_above": 256
},
"url": {
"type": "keyword",
"ignore_above": 256,
"normalizer": "lowercase_normalizer"
},
"event_type": {
"type": "keyword",
"ignore_above": 256
},
"server_hostname": {
"type": "keyword",
"ignore_above": 256,
"normalizer": "lowercase_normalizer"
},
"stack_md5": {
"type": "keyword",
"ignore_above": 64
},
"server_type": {
"type": "keyword",
"ignore_above": 256
},
"server_version": {
"type": "keyword",
"ignore_above": 256
},
"request_id": {
"type": "keyword",
"ignore_above": 256
},
"body": {
"type": "keyword"
},
"app_id": {
"type": "keyword",
"ignore_above": 256
},
"rasp_id": {
"type": "keyword",
"ignore_above": 256
},
"event_time": {
"type": "date"
},
"stack_trace": {
"type": "keyword"
},
"intercept_state": {
"type": "keyword",
"ignore_above": 64
},
"attack_type": {
"type": "keyword",
"ignore_above": 256
},
"attack_location": {
"type": "object",
"properties": {
"location_zh_cn":{
"type": "keyword",
"ignore_above": 256
},
"location_en":{
"type": "keyword",
"ignore_above": 256
},
"longitude":{
"type": "double"
},
"latitude":{
"type": "double"
}
}
},
"plugin_algorithm":{
"type": "keyword",
"ignore_above": 256
},
"plugin_name": {
"type": "keyword",
"ignore_above": 256
},
"plugin_confidence": {
"type": "short"
},
"attack_params": {
"type": "object",
"enabled":"false"
},
"plugin_message": {
"type": "keyword"
},
"server_nic": {
"type": "nested",
"properties": {
"name": {
"type": "keyword",
"ignore_above": 256
},
"ip": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
}
`
var policyAlarmTemplate = `
{
"template":"openrasp-policy-alarm-*",
"aliases" : {
"real-{index}" : {}
},
"settings": {
"analysis": {
"normalizer": {
"lowercase_normalizer": {
"type": "custom",
"filter": ["lowercase"]
}
}
}
},
"mappings": {
"policy-alarm": {
"_all": {
"enabled": false
},
"properties": {
"@timestamp":{
"type":"date"
},
"event_type": {
"type": "keyword",
"ignore_above": 256
},
"server_hostname": {
"type": "keyword",
"ignore_above": 256,
"normalizer": "lowercase_normalizer"
},
"server_type": {
"type": "keyword",
"ignore_above": 64
},
"server_nic": {
"type": "nested",
"properties": {
"name": {
"type": "keyword",
"ignore_above": 256
},
"ip": {
"type": "keyword",
"ignore_above": 256
}
}
},
"app_id": {
"type": "keyword",
"ignore_above": 256
},
"rasp_id": {
"type": "keyword",
"ignore_above": 256
},
"event_time": {
"type": "date"
},
"stack_trace": {
"type": "keyword"
},
"policy_id": {
"type": "long"
},
"message": {
"type": "keyword"
},
"stack_md5": {
"type": "keyword",
"ignore_above": 64
},
"policy_params": {
"type": "object",
"enabled":"false"
}
}
}
}
}
`
var errorAlarmTemplate = `{
"template":"openrasp-error-alarm-*",
"aliases" : {
"real-{index}" : {}
},
"settings": {
"analysis": {
"normalizer": {
"lowercase_normalizer": {
"type": "custom",
"filter": ["lowercase"]
}
}
}
},
"mappings": {
"error-alarm": {
"_all": {
"enabled": false
},
"properties": {
"@timestamp":{
"type":"date"
},
"app_id": {
"type": "keyword",
"ignore_above": 256
},
"rasp_id": {
"type": "keyword",
"ignore_above": 256
},
"message": {
"type": "keyword"
},
"level": {
"type": "keyword",
"ignore_above": 64
},
"err_code":{
"type": "long"
},
"stack_trace":{
"type": "keyword"
},
"pid":{
"type": "long"
},
"event_time": {
"type": "date"
},
"server_hostname": {
"type": "keyword",
"ignore_above": 256,
"normalizer": "lowercase_normalizer"
},
"server_nic": {
"type": "nested",
"properties": {
"name": {
"type": "keyword",
"ignore_above": 256
},
"ip": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
}
`
var reportDataTemplate = `
{
"template":"openrasp-report-data-*",
"aliases" : {
"real-{index}" : {}
},
"mappings": {
"report-data": {
"_all": {
"enabled": false
},
"properties": {
"@timestamp":{
"type":"date"
},
"time": {
"type": "date"
},
"request_sum": {
"type": "long"
},
"rasp_id": {
"type": "keyword",
"ignore_above" : 256
}
}
}
}
}
`
func init() {
if *conf.AppConfig.Flag.StartType != conf.StartTypeReset {
templates := map[string]string{
"report-data-template": reportDataTemplate,
"error-alarm-template": errorAlarmTemplate,
"attack-alarm-template": attackAlarmTemplate,
"policy-alarm-template": policyAlarmTemplate,
}
for name, template := range templates {
err := CreateTemplate(name, template)
if err != nil {
tools.Panic(tools.ErrCodeESInitFailed, "failed to create es template: "+name, err)
}
}
}
}
//Copyright 2017-2019 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package models
import (
"rasp-cloud/mongo"
"fmt"
"strconv"
"time"
"math/rand"
"rasp-cloud/tools"
"gopkg.in/mgo.v2"
"crypto/sha1"
"gopkg.in/mgo.v2/bson"
"rasp-cloud/models/logs"
"github.com/astaxie/beego"
"net/smtp"
"os"
"net/mail"
"strings"
"html/template"
"bytes"
"github.com/astaxie/beego/httplib"
"errors"
"crypto/sha256"
"encoding/base64"
"io/ioutil"
"crypto/tls"
"net"
"rasp-cloud/conf"
"net/url"
)
type App struct {
Id string `json:"id" bson:"_id"`
Name string `json:"name" bson:"name"`
Secret string `json:"secret" bson:"secret"`
Language string `json:"language" bson:"language"`
Description string `json:"description" bson:"description"`
CreateTime int64 `json:"create_time" bson:"create_time"`
ConfigTime int64 `json:"config_time" bson:"config_time"`
GeneralConfig map[string]interface{} `json:"general_config" bson:"general_config"`
WhitelistConfig []WhitelistConfigItem `json:"whitelist_config" bson:"whitelist_config"`
SelectedPluginId string `json:"selected_plugin_id" bson:"selected_plugin_id"`
EmailAlarmConf EmailAlarmConf `json:"email_alarm_conf" bson:"email_alarm_conf"`
DingAlarmConf DingAlarmConf `json:"ding_alarm_conf" bson:"ding_alarm_conf"`
HttpAlarmConf HttpAlarmConf `json:"http_alarm_conf" bson:"http_alarm_conf"`
AlgorithmConfig map[string]interface{} `json:"algorithm_config"`
}
type WhitelistConfigItem struct {
Url string `json:"url" bson:"url"`
Hook map[string]bool `json:"hook" bson:"hook"`
}
type EmailAlarmConf struct {
Enable bool `json:"enable" bson:"enable"`
From string `json:"from" bson:"from"`
ServerAddr string `json:"server_addr" bson:"server_addr"`
UserName string `json:"username" bson:"username"`
Password string `json:"password" bson:"password"`
Subject string `json:"subject" bson:"subject"`
RecvAddr []string `json:"recv_addr" bson:"recv_addr"`
TlsEnable bool `json:"tls_enable" bson:"tls_enable"`
}
type DingAlarmConf struct {
Enable bool `json:"enable" bson:"enable"`
AgentId string `json:"agent_id" bson:"agent_id"`
CorpId string `json:"corp_id" bson:"corp_id"`
CorpSecret string `json:"corp_secret" bson:"corp_secret"`
RecvUser []string `json:"recv_user" bson:"recv_user"`
RecvParty []string `json:"recv_party" bson:"recv_party"`
}
type HttpAlarmConf struct {
Enable bool `json:"enable" bson:"enable"`
RecvAddr []string `json:"recv_addr" bson:"recv_addr"`
}
type emailTemplateParam struct {
Total int64
Alarms []map[string]interface{}
DetailedLink string
AppName string
HttpPort int
}
type dingResponse struct {
ErrCode int64 `json:"errcode"`
ErrMsg string `json:"errmsg"`
AccessToken string `json:"access_token"`
}
const (
appCollectionName = "app"
defaultAppName = "PHP 示例应用"
SecreteMask = "************"
)
var (
lastAlarmTime = time.Now().UnixNano() / 1000000
DefaultGeneralConfig = map[string]interface{}{
"clientip.header": "ClientIP",
"block.status_code": 302,
"block.redirect_url": "https://rasp.baidu.com/blocked/?request_id=%request_id%",
"block.content_xml": "<?xml version=\"1.0\"?><doc><error>true</error><reason>Request blocked by OpenRASP" +
"</reason><request_id>%request_id%</request_id></doc>",
"block.content_html": "</script><script>" +
"location.href=\"https://rasp.baidu.com/blocked2/?request_id=%request_id%\"</script>",
"block.content_json": `{"error":true,"reason": "Request blocked by OpenRASP","request_id": "%request_id%"}`,
"plugin.timeout.millis": 100,
"body.maxbytes": 4096,
"plugin.filter": true,
"plugin.maxstack": 100,
"ognl.expression.minlength": 30,
"log.maxstack": 50,
"log.maxburst": 100,
"log.maxbackup": 30,
"syslog.tag": "OpenRASP",
"syslog.url": "",
"syslog.facility": 1,
"syslog.enable": false,
"decompile.enable": false,
}
)
func init() {
count, err := mongo.Count(appCollectionName)
if err != nil {
tools.Panic(tools.ErrCodeMongoInitFailed, "failed to get app collection count", err)
}
index := &mgo.Index{
Key: []string{"name"},
Unique: true,
Background: true,
Name: "app_name",
}
err = mongo.CreateIndex(appCollectionName, index)
if err != nil {
tools.Panic(tools.ErrCodeMongoInitFailed, "failed to create index for app collection", err)
}
if *conf.AppConfig.Flag.StartType == conf.StartTypeDefault ||
*conf.AppConfig.Flag.StartType == conf.StartTypeForeground {
if count <= 0 {
createDefaultApp()
}
go startAlarmTicker(time.Second * time.Duration(conf.AppConfig.AlarmCheckInterval))
}
if *conf.AppConfig.Flag.StartType != conf.StartTypeReset {
initApp()
}
}
func initApp() error {
var apps []*App
_, err := mongo.FindAllWithoutLimit(appCollectionName, nil, &apps)
if err != nil {
tools.Panic(tools.ErrCodeMongoInitFailed, "failed to get all app", err)
}
for _, app := range apps {
err := createEsIndexWithAppId(app.Id)
if err != nil {
tools.Panic(tools.ErrCodeESInitFailed, "failed to init es index for app "+app.Name, err)
}
if *conf.AppConfig.Flag.StartType != conf.StartTypeAgent {
err := initPlugin(app)
if err != nil {
beego.Warn(tools.ErrCodeInitDefaultAppFailed, "failed to init plugin for app ["+app.Name+"]", err)
}
}
}
return nil
}
func initPlugin(app *App) error {
content, err := getDefaultPluginContent()
if err != nil {
return err
}
plugin, err := generatePlugin(content, app.Id)
if err != nil {
return err
}
err = mongo.FindOne(pluginCollectionName, bson.M{
"name": plugin.Name,
"version": plugin.Version,
"app_id": plugin.AppId}, plugin)
if err != nil {
if err == mgo.ErrNotFound {
err = addPluginToDb(plugin)
if err != nil {
return err
}
} else {
return err
}
}
return nil
}
func createEsIndexWithAppId(appId string) error {
err := logs.CreateAlarmEsIndex(appId)
if err != nil {
return errors.New("failed to create alarm es index, " + err.Error())
}
err = CreateReportDataEsIndex(appId)
if err != nil {
return errors.New("failed to create report data es index, " + err.Error())
}
return nil
}
func createDefaultApp() {
_, err := AddApp(&App{
Name: defaultAppName,
Description: "default app",
Language: "php",
})
if err != nil {
tools.Panic(tools.ErrCodeInitDefaultAppFailed, "failed to create default app", err)
}
}
func startAlarmTicker(interval time.Duration) {
ticker := time.NewTicker(interval)
for {
select {
case <-ticker.C:
HandleAttackAlarm()
}
}
}
func HandleAttackAlarm() {
defer func() {
if r := recover(); r != nil {
beego.Error("failed to handle alarm: ", r)
}
}()
var apps []App
_, err := mongo.FindAllWithSelect(appCollectionName, nil, &apps, bson.M{"plugin": 0}, 0, 0)
if err != nil {
beego.Error("failed to get apps for the alarm: " + err.Error())
return
}
now := time.Now().UnixNano() / 1000000
for _, app := range apps {
total, result, err := logs.SearchLogs(lastAlarmTime, now, false, nil, "event_time",
1, 10, false, logs.AttackAlarmInfo.EsAliasIndex+"-"+app.Id)
if err != nil {
beego.Error("failed to get alarm from es: " + err.Error())
continue
}
if total > 0 {
PushAttackAlarm(&app, total, result, false)
}
}
lastAlarmTime = now + 1
}
func AddApp(app *App) (result *App, err error) {
app.Id = generateAppId(app)
app.Secret = generateSecret(app)
app.CreateTime = time.Now().Unix()
if err := mongo.FindOne(appCollectionName, bson.M{"name": app.Name}, &App{}); err != mgo.ErrNotFound {
if err != nil {
return nil, err
}
return nil, errors.New("duplicate app name")
}
HandleApp(app, true)
err = createEsIndexWithAppId(app.Id)
if err != nil {
return nil, errors.New("failed to create es index for app " + app.Name + ", " + err.Error())
}
// ES must be created before mongo
err = mongo.Insert(appCollectionName, app)
if err != nil {
return nil, errors.New("failed to insert app to db: " + err.Error())
}
result = app
beego.Info("Succeed to create app, name: " + app.Name)
selectDefaultPlugin(app)
return
}
func getDefaultPluginContent() ([]byte, error) {
// if setting default plugin fails, continue to initialize
currentPath, err := tools.GetCurrentPath()
if err != nil {
return nil, errors.New("failed to get get default plugin directory: " + err.Error())
}
content, err := ioutil.ReadFile(currentPath + "/resources/plugin.js")
if err != nil {
return nil, errors.New("failed to read default plugin content: " + err.Error())
}
return content, err
}
func selectDefaultPlugin(app *App) {
// if setting default plugin fails, continue to initialize
content, err := getDefaultPluginContent()
if err != nil {
beego.Warn(tools.ErrCodeInitDefaultAppFailed, "failed to get default plugin: "+err.Error())
return
}
plugin, err := AddPlugin(content, app.Id)
if err != nil {
beego.Warn(tools.ErrCodeInitDefaultAppFailed, "failed to insert default plugin: "+err.Error())
return
}
_, err = SetSelectedPlugin(app.Id, plugin.Id)
if err != nil {
beego.Warn(tools.ErrCodeInitDefaultAppFailed, "failed to select default plugin for app: " + err.Error()+
", app_id: "+ app.Id+ ", plugin_id: "+ plugin.Id)
return
}
beego.Info("Succeed to set up default plugin for app, version: " + plugin.Version)
}
func generateAppId(app *App) string {
random := "openrasp_app" + app.Name + strconv.FormatInt(time.Now().UnixNano(), 10) + strconv.Itoa(rand.Intn(10000))
return fmt.Sprintf("%x", sha1.Sum([]byte(random)))
}
func generateSecret(app *App) string {
random := "openrasp_app" + app.Name + app.Id +
strconv.FormatInt(time.Now().UnixNano(), 10) + strconv.Itoa(rand.Intn(10000))
sha256Data := sha256.Sum256([]byte(random))
base64Data := base64.NewEncoding("OPQRSTYZabcdefgABCDEFGHIJKLMNhijklmnopqrUVWXstuvwxyz01234567891q").
EncodeToString(sha256Data[0:])
return base64Data[0 : len(base64Data)-1]
}
func GetAllApp(page int, perpage int, mask bool) (count int, result []*App, err error) {
count, err = mongo.FindAll(appCollectionName, nil, &result, perpage*(page-1), perpage, "name")
if err == nil && result != nil {
for _, app := range result {
if mask {
HandleApp(app, false)
}
}
}
return
}
func GetAppByIdWithoutMask(id string) (app *App, err error) {
err = mongo.FindId(appCollectionName, id, &app)
return
}
func GetAppById(id string) (app *App, err error) {
err = mongo.FindId(appCollectionName, id, &app)
if err == nil && app != nil {
HandleApp(app, false)
}
return
}
func GetSecretByAppId(appId string) (secret string, err error) {
newSession := mongo.NewSession()
defer newSession.Close()
var result *App
err = newSession.DB(mongo.DbName).C(appCollectionName).FindId(appId).Select(bson.M{"secret": 1}).One(&result)
if err != nil {
return
}
if result != nil {
secret = result.Secret
}
return
}
func RegenerateSecret(appId string) (secret string, err error) {
var app *App
err = mongo.FindId(appCollectionName, appId, &app)
if err != nil {
return
}
newSecret := generateSecret(app)
err = mongo.UpdateId(appCollectionName, appId, bson.M{"secret": newSecret})
return
}
func HandleApp(app *App, isCreate bool) error {
if app.EmailAlarmConf.RecvAddr == nil {
app.EmailAlarmConf.RecvAddr = make([]string, 0)
}
if app.DingAlarmConf.RecvParty == nil {
app.DingAlarmConf.RecvParty = make([]string, 0)
}
if app.DingAlarmConf.RecvUser == nil {
app.DingAlarmConf.RecvUser = make([]string, 0)
}
if app.HttpAlarmConf.RecvAddr == nil {
app.HttpAlarmConf.RecvAddr = make([]string, 0)
}
if !isCreate {
if app.EmailAlarmConf.Password != "" {
app.EmailAlarmConf.Password = SecreteMask
}
if app.DingAlarmConf.CorpSecret != "" {
app.DingAlarmConf.CorpSecret = SecreteMask
}
} else {
if app.GeneralConfig == nil {
app.GeneralConfig = DefaultGeneralConfig
}
}
if app.WhitelistConfig == nil {
app.WhitelistConfig = make([]WhitelistConfigItem, 0)
}
if app.GeneralConfig == nil {
app.GeneralConfig = make(map[string]interface{})
}
app.AlgorithmConfig = make(map[string]interface{})
plugin, err := GetSelectedPlugin(app.Id, false)
if err != nil {
if err != mgo.ErrNotFound {
return err
}
return nil
}
if plugin != nil && plugin.AlgorithmConfig != nil {
app.AlgorithmConfig = plugin.AlgorithmConfig
}
return nil
}
func UpdateAppById(id string, doc interface{}) (app *App, err error) {
err = mongo.UpdateId(appCollectionName, id, doc)
if err != nil {
return
}
return GetAppById(id)
}
func UpdateGeneralConfig(appId string, config map[string]interface{}) (*App, error) {
return UpdateAppById(appId, bson.M{"general_config": config, "config_time": time.Now().UnixNano()})
}
func UpdateWhiteListConfig(appId string, config []WhitelistConfigItem) (app *App, err error) {
return UpdateAppById(appId, bson.M{"whitelist_config": config, "config_time": time.Now().UnixNano()})
}
func RemoveAppById(id string) (app *App, err error) {
err = mongo.FindId(appCollectionName, id, &app)
if err != nil {
return
}
return app, mongo.RemoveId(appCollectionName, id)
}
func GetAppCount() (count int, err error) {
return mongo.Count(appCollectionName)
}
func PushAttackAlarm(app *App, total int64, alarms []map[string]interface{}, isTest bool) {
if app != nil {
if app.DingAlarmConf.Enable {
PushDingAttackAlarm(app, total, alarms, isTest)
}
if app.EmailAlarmConf.Enable {
PushEmailAttackAlarm(app, total, alarms, isTest)
}
if app.HttpAlarmConf.Enable {
PushHttpAttackAlarm(app, total, alarms, isTest)
}
}
}
func getTestAlarmData() []map[string]interface{} {
return []map[string]interface{}{
{
"event_time": time.Now().Format("2006-01-01 15:06:05"),
"attack_source": "220.181.57.191",
"attack_type": "sql",
"intercept_state": "block",
"url": "http://www.example.com/article.php?id=1",
},
{
"event_time": time.Now().Format("2006-01-01 15:03:01"),
"attack_source": "220.23.38.115",
"attack_type": "command",
"intercept_state": "log",
"url": "http://www.example.com/login.php?id=2",
"client_ip": "220.22.13.3",
},
}
}
func PushEmailAttackAlarm(app *App, total int64, alarms []map[string]interface{}, isTest bool) error {
var emailConf = app.EmailAlarmConf
if len(emailConf.RecvAddr) > 0 && emailConf.ServerAddr != "" {
var (
subject string
msg string
emailAddr = &mail.Address{Address: emailConf.UserName}
)
if emailConf.From != "" {
emailAddr.Name = emailConf.From
} else {
hostName, err := os.Hostname()
if err == nil {
emailAddr.Name = hostName
} else {
emailAddr.Name = "OpenRASP"
}
}
if emailConf.Subject == "" {
subject = "OpenRASP alarm"
} else {
subject = emailConf.Subject
}
if isTest {
subject = "【测试邮件】" + subject
alarms = getTestAlarmData()
total = int64(len(alarms))
}
head := map[string]string{
"From": emailAddr.String(),
"To": strings.Join(emailConf.RecvAddr, ","),
"Subject": subject,
"Content-Type": "text/html; charset=UTF-8",
"X-Priority": "3",
"X-MSMail-Priority": "Normal",
"X-Mailer": "Microsoft Outlook Express 6.00.2900.2869",
"X-MimeOLE": "Produced By Microsoft MimeOLE V6.00.2900.2869",
"ReturnReceipt": "1",
}
t, err := template.ParseFiles("views/email.tpl")
if err != nil {
beego.Error("failed to render email template: " + err.Error())
return err
}
alarmData := new(bytes.Buffer)
panelUrl, port := getPanelServerUrl()
handleAlarms(alarms)
err = t.Execute(alarmData, &emailTemplateParam{
Total: total - int64(len(alarms)),
Alarms: alarms,
AppName: app.Name,
DetailedLink: panelUrl + "/#/events/" + app.Id,
HttpPort: port,
})
if err != nil {
beego.Error("failed to execute email template: " + err.Error())
return err
}
for k, v := range head {
msg += fmt.Sprintf("%s: %s\r\n", k, v)
}
msg += "\r\n" + alarmData.String()
if !strings.Contains(emailConf.ServerAddr, ":") {
if emailConf.TlsEnable {
emailConf.ServerAddr += ":465"
} else {
emailConf.ServerAddr += ":25"
}
}
//host, _, err := net.SplitHostPort(emailConf.ServerAddr)
//if err != nil {
// return handleError("failed to get email serve host: " + err.Error())
//}
auth := tools.LoginAuth(emailConf.UserName, emailConf.Password)
//auth := smtp.PlainAuth("", emailConf.UserName, emailConf.Password, host)
//auth := smtp.CRAMMD5Auth(emailConf.UserName, emailConf.Password)
if emailConf.Password == "" {
auth = nil
}
if emailConf.TlsEnable {
return sendEmailWithTls(emailConf, auth, msg)
}
return sendNormalEmail(emailConf, auth, msg)
} else {
beego.Error(
"failed to send email alarm: the email receiving address and email server address can not be empty", emailConf)
return errors.New("the email receiving address and email server address can not be empty")
}
}
func handleAlarms(alarms []map[string]interface{}) {
for index, alarm := range alarms {
alarm["index"] = index + 1
if intercept, ok := logs.AttackInterceptMap[alarm["intercept_state"]]; ok {
alarm["intercept_state"] = intercept
}
if attackType, ok := logs.AttackTypeMap[alarm["attack_type"]]; ok {
alarm["attack_type"] = attackType
} else {
alarm["attack_type"] = "其他类型"
}
if alarm["url"] != nil {
if attackUrl, ok := alarm["url"].(string); ok && attackUrl != "" {
attackUrl, err := url.Parse(attackUrl)
if err == nil {
alarm["domain"] = attackUrl.Host
}
}
}
if alarm["client_ip"] != nil {
if clientIp, ok := alarm["client_ip"].(string); ok && clientIp != "" {
alarm["attack_source"] = clientIp
}
}
}
}
func getPanelServerUrl() (string, int) {
serverUrl, err := GetServerUrl()
if err != nil && err != mgo.ErrNotFound {
beego.Error("failed to get panel url for alarm: " + err.Error())
}
port := beego.AppConfig.DefaultInt("httpport", 8080)
if serverUrl == nil || len(serverUrl.PanelUrl) == 0 {
return "", port
}
return serverUrl.PanelUrl, port
}
func sendNormalEmail(emailConf EmailAlarmConf, auth smtp.Auth, msg string) (err error) {
err = smtp.SendMail(emailConf.ServerAddr, auth, emailConf.UserName, emailConf.RecvAddr, []byte(msg))
if err != nil {
beego.Error("failed to push email alarms: " + err.Error())
return
}
return
}
func sendEmailWithTls(emailConf EmailAlarmConf, auth smtp.Auth, msg string) error {
client, err := smtpTlsDial(emailConf.ServerAddr)
if err != nil {
return handleError("failed to start tls: " + err.Error())
}
if client.Text != nil {
defer client.Close()
}
if auth != nil {
if ok, _ := client.Extension("AUTH"); ok {
if err = client.Auth(auth); err != nil {
return handleError("failed to auth with tls: " + err.Error())
}
}
}
if err = client.Mail(emailConf.UserName); err != nil {
return handleError("failed to mail from 'emailConf.UserName': " + err.Error())
}
for _, addr := range emailConf.RecvAddr {
if err = client.Rcpt(addr); err != nil {
return handleError("failed to push email to " + addr + " with tls: " + err.Error())
}
}
writer, err := client.Data()
if err != nil {
return handleError("failed to get writer for email with tls: " + err.Error())
}
defer writer.Close()
_, err = writer.Write([]byte(msg))
if err != nil {
return handleError("failed to write msg with tls: " + err.Error())
}
if client.Text != nil {
client.Quit()
}
return nil
}
func handleError(msg string) error {
beego.Error(msg)
return errors.New(msg)
}
func smtpTlsDial(addr string) (*smtp.Client, error) {
host, _, err := net.SplitHostPort(addr)
if err != nil {
return nil, handleError("failed to get email serve host: " + err.Error())
}
conn, err := tls.DialWithDialer(&net.Dialer{Timeout: time.Second * 5}, "tcp",
addr, &tls.Config{InsecureSkipVerify: true})
if err != nil {
return nil, handleError("smtp dialing error: " + err.Error())
}
return smtp.NewClient(conn, host)
}
func PushHttpAttackAlarm(app *App, total int64, alarms []map[string]interface{}, isTest bool) error {
var httpConf = app.HttpAlarmConf
if len(httpConf.RecvAddr) != 0 {
body := make(map[string]interface{})
body["app_id"] = app.Id
if isTest {
body["data"] = getTestAlarmData()
} else {
body["data"] = alarms
}
for _, addr := range httpConf.RecvAddr {
request := httplib.Post(addr)
request.JSONBody(body)
request.SetTimeout(10*time.Second, 10*time.Second)
response, err := request.Response()
if err != nil {
return handleError("failed to push http alarms to: " + addr + ", with error: " + err.Error())
}
if response.StatusCode > 299 || response.StatusCode < 200 {
return handleError("failed to push http alarms to: " + addr + ", with status code: " +
strconv.Itoa(response.StatusCode))
}
}
} else {
return handleError("failed to send http alarm: the http receiving address can not be empty")
}
beego.Debug("succeed in pushing http alarm for app: " + app.Name + " ,with urls: " +
fmt.Sprintf("%v", httpConf.RecvAddr))
return nil
}
func PushDingAttackAlarm(app *App, total int64, alarms []map[string]interface{}, isTest bool) error {
var dingCong = app.DingAlarmConf
if dingCong.CorpId != "" && dingCong.CorpSecret != "" && dingCong.AgentId != "" &&
!(len(dingCong.RecvParty) == 0 && len(dingCong.RecvUser) == 0) {
request := httplib.Get("https://oapi.dingtalk.com/gettoken")
request.SetTimeout(10*time.Second, 10*time.Second)
request.Param("corpid", dingCong.CorpId)
request.Param("corpsecret", dingCong.CorpSecret)
response, err := request.Response()
errMsg := "failed to get ding ding token with corp id: " + dingCong.CorpId
if err != nil {
return handleError(errMsg + ", with error: " + err.Error())
}
if response.StatusCode != 200 {
return handleError(errMsg + ", with status code: " + strconv.Itoa(response.StatusCode))
}
var result dingResponse
err = request.ToJSON(&result)
if err != nil {
return handleError(errMsg + ", with error: " + err.Error())
}
if result.ErrCode != 0 {
return handleError(errMsg + ", with errmsg: " + result.ErrMsg)
}
token := result.AccessToken
body := make(map[string]interface{})
dingText := ""
if isTest {
dingText = "OpenRASP test message from app: " + app.Name + ", time: " + time.Now().Format(time.RFC3339)
} else {
panelUrl, _ := getPanelServerUrl()
if len(panelUrl) == 0 {
panelUrl = "http://127.0.0.1"
}
dingText = "时间:" + time.Now().Format(time.RFC3339) + ", 来自 OpenRAS 的报警\n共有 " +
strconv.FormatInt(total, 10) + " 条报警信息来自 APP:" + app.Name + ",详细信息:" +
panelUrl + "/#/events/" + app.Id
}
if len(dingCong.RecvUser) > 0 {
body["touser"] = strings.Join(dingCong.RecvUser, "|")
}
if len(dingCong.RecvParty) > 0 {
body["toparty"] = strings.Join(dingCong.RecvParty, "|")
}
body["agentid"] = dingCong.AgentId
body["msgtype"] = "text"
body["text"] = map[string]string{"content": dingText}
request = httplib.Post("https://oapi.dingtalk.com/message/send?access_token=" + token)
request.JSONBody(body)
request.SetTimeout(10*time.Second, 10*time.Second)
response, err = request.Response()
errMsg = "failed to push ding ding alarms with corp id: " + dingCong.CorpId
if err != nil {
return handleError(errMsg + ", with error: " + err.Error())
}
if response.StatusCode != 200 {
return handleError(errMsg + ", with status code: " + strconv.Itoa(response.StatusCode))
}
err = request.ToJSON(&result)
if err != nil {
return handleError(errMsg + ", with error: " + err.Error())
}
if result.ErrCode != 0 {
return handleError(errMsg + ", with errmsg: " + result.ErrMsg)
}
} else {
return handleError("failed to send ding ding alarm: invalid ding ding alarm conf")
}
beego.Debug("succeed in pushing ding ding alarm for app: " + app.Name + " ,with corp id: " + dingCong.CorpId)
return nil
}
//Copyright 2017-2019 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package models
import (
"time"
"rasp-cloud/mongo"
"rasp-cloud/tools"
"gopkg.in/mgo.v2"
"rasp-cloud/conf"
"gopkg.in/mgo.v2/bson"
)
type Cookie struct {
Id string `json:"id" bson:"_id"`
Time time.Time `json:"time" bson:"time"`
}
const (
cookieCollectionName = "cookie"
AuthCookieName = "RASP_AUTH_ID"
)
func init() {
index := &mgo.Index{
Key: []string{"time"},
Background: true,
Name: "time",
ExpireAfter: time.Duration(conf.AppConfig.CookieLifeTime) * time.Hour,
}
err := mongo.CreateIndex(cookieCollectionName, index)
if err != nil {
tools.Panic(tools.ErrCodeMongoInitFailed, "failed to create index for app collection", err)
}
}
func NewCookie(id string) error {
return mongo.Insert(cookieCollectionName, &Cookie{Id: id, Time: time.Now()})
}
func HasCookie(id string) (bool, error) {
var result *Cookie
err := mongo.FindId(cookieCollectionName, id, &result)
if err != nil || result == nil {
return false, err
}
return true, err
}
func RemoveCookie(id string) error {
return mongo.RemoveId(cookieCollectionName, id)
}
func RemoveAllCookie() error {
_, err := mongo.RemoveAll(cookieCollectionName, bson.M{})
return err
}
//Copyright 2017-2019 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package logs
import (
"crypto/md5"
"fmt"
"rasp-cloud/es"
"github.com/olivere/elastic"
"time"
"context"
"github.com/oschwald/geoip2-golang"
"github.com/astaxie/beego"
"net"
"rasp-cloud/tools"
"encoding/json"
"rasp-cloud/conf"
)
var (
AttackAlarmInfo = AlarmLogInfo{
EsType: "attack-alarm",
EsIndex: "openrasp-attack-alarm",
EsAliasIndex: "real-openrasp-attack-alarm",
TtlTime: 24 * 365 * time.Hour,
AlarmBuffer: make(chan map[string]interface{}, conf.AppConfig.AlarmBufferSize),
FileLogger: initAlarmFileLogger("/openrasp-logs/attack-alarm", "attack.log"),
}
geoIpDbPath string
geoIpDb *geoip2.Reader
AttackTypeMap = map[interface{}]string{
"sql": "SQL 注入",
"sql_exception": "SQL 语句异常",
"command": "命令执行",
"xxe": "XXE 外部实体加载",
"directory": "目录遍历",
"rename": "文件重命名",
"readFile": "任意文件下载",
"include": "任意文件包含",
"writeFile": "任意文件写入",
"ssrf": "SSRF 服务端请求伪造",
"ognl": "OGNL 代码执行",
"webdav": "任意文件上传 (PUT)",
"fileUpload": "任意文件上传",
"deserialization": "Transformer 反序列化",
"xss_echo": "Echo XSS 跨站脚本攻击",
"xss_userinput": "BODY XSS 跨站脚本攻击",
"webshell_callable": "WebShell - 变形后门",
"webshell_eval": "WebShell - 中国菜刀",
"webshell_command": "WebShell - 命令执行",
"webshell_file_put_contents": "WebShell - 后门上传",
"webshell_ld_preload": "WebShell - LD_PRELOAD 后门",
}
AttackInterceptMap = map[interface{}]string{
"block": "拦截请求",
"log": "记录日志",
}
)
func init() {
var err error
registerAlarmInfo(&AttackAlarmInfo)
geoIpDbPath = tools.GetCurrentPathWithPanic() + "/geoip/GeoLite2-City.mmdb"
geoIpDb, err = geoip2.Open(geoIpDbPath)
if err != nil {
tools.Panic(tools.ErrCodeGeoipInit, "failed to open geoip database", err)
}
}
func AddAttackAlarm(alarm map[string]interface{}) error {
defer func() {
if r := recover(); r != nil {
beego.Error("failed to add attack alarm: ", r)
}
}()
if stack, ok := alarm["stack_trace"]; ok && stack != nil && stack != "" {
_, ok = stack.(string)
if ok {
alarm["stack_md5"] = fmt.Sprintf("%x", md5.Sum([]byte(stack.(string))))
}
}
setAlarmLocation(alarm)
return AddAlarmFunc(AttackAlarmInfo.EsType, alarm)
}
func setAlarmLocation(alarm map[string]interface{}) {
if attackSource, ok := alarm["attack_source"]; ok && attackSource != nil {
_, ok = attackSource.(string)
if ok {
attackIp := net.ParseIP(attackSource.(string))
record, err := geoIpDb.City(attackIp)
if err != nil {
beego.Error("failed to parse attack ip to location: " + err.Error())
}
if record != nil {
alarm["attack_location"] = map[string]interface{}{
"location_zh_cn": record.Country.Names["zh-CN"] + "-" + record.City.Names["zh-CN"],
"location_en": record.Country.Names["en"] + "-" + record.City.Names["en"],
"latitude": record.Location.Latitude,
"longitude": record.Location.Longitude,
}
}
}
}
}
func AggregationAttackWithTime(startTime int64, endTime int64, interval string, timeZone string,
appId string) (map[string]interface{}, error) {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(10*time.Second))
defer cancel()
timeAggrName := "aggr_time"
interceptAggrName := "request_sum"
timeAggr := elastic.NewDateHistogramAggregation().Field("event_time").TimeZone(timeZone).
Interval(interval).ExtendedBounds(startTime, endTime)
interceptAggr := elastic.NewTermsAggregation().Field("intercept_state")
timeAggr.SubAggregation(interceptAggrName, interceptAggr)
timeQuery := elastic.NewRangeQuery("event_time").Gte(startTime).Lte(endTime)
aggrResult, err := es.ElasticClient.Search(AttackAlarmInfo.EsAliasIndex + "-" + appId).
Query(elastic.NewBoolQuery().Must(timeQuery)).
Aggregation(timeAggrName, timeAggr).
Size(0).
Do(ctx)
if err != nil {
if aggrResult != nil && aggrResult.Error != nil {
errMsg, err := json.Marshal(aggrResult.Error)
if err != nil {
beego.Error(string(errMsg))
}
}
return nil, err
}
var dataResult = make([][]int64, 2)
dataResult[0] = make([]int64, 0)
dataResult[1] = make([]int64, 0)
var labels = make([]interface{}, 0)
var result = make(map[string]interface{})
if aggrResult != nil && aggrResult.Aggregations != nil {
if terms, ok := aggrResult.Aggregations.Terms(timeAggrName); ok && terms.Buckets != nil {
labelCount := len(terms.Buckets)
dataResult[0] = make([]int64, labelCount)
dataResult[1] = make([]int64, labelCount)
labels = make([]interface{}, labelCount)
for index, timeTerm := range terms.Buckets {
labels[index] = timeTerm.Key
if interceptTerm, ok := timeTerm.Terms(interceptAggrName); ok {
for _, item := range interceptTerm.Buckets {
if item.Key == "block" {
dataResult[0][index] = item.DocCount
} else if item.Key == "log" {
dataResult[1][index] = item.DocCount
}
}
}
}
}
}
result["data"] = dataResult
result["labels"] = labels
return result, nil
}
func AggregationAttackWithUserAgent(startTime int64, endTime int64, size int,
appId string) ([][]interface{}, error) {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(10*time.Second))
defer cancel()
uaAggr := elastic.NewTermsAggregation().Field("user_agent").Size(size).OrderByCount(false)
timeQuery := elastic.NewRangeQuery("event_time").Gte(startTime).Lte(endTime)
aggrName := "aggr_ua"
aggrResult, err := es.ElasticClient.Search(AttackAlarmInfo.EsAliasIndex + "-" + appId).
Query(timeQuery).
Aggregation(aggrName, uaAggr).
Size(0).
Do(ctx)
if err != nil {
if aggrResult != nil && aggrResult.Error != nil {
errMsg, err := json.Marshal(aggrResult.Error)
if err != nil {
beego.Error(string(errMsg))
}
}
return nil, err
}
result := make([][]interface{}, 0)
if aggrResult != nil && aggrResult.Aggregations != nil {
if terms, ok := aggrResult.Aggregations.Terms(aggrName); ok && terms.Buckets != nil {
result = make([][]interface{}, len(terms.Buckets))
for index, item := range terms.Buckets {
result[index] = make([]interface{}, 2, 2)
result[index][0] = item.Key
result[index][1] = item.DocCount
}
}
}
return result, nil
}
func AggregationAttackWithType(startTime int64, endTime int64, size int,
appId string) ([][]interface{}, error) {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(10*time.Second))
defer cancel()
typeAggr := elastic.NewTermsAggregation().Field("attack_type").Size(size).OrderByCount(false)
timeQuery := elastic.NewRangeQuery("event_time").Gte(startTime).Lte(endTime)
aggrName := "aggr_type"
aggrResult, err := es.ElasticClient.Search(AttackAlarmInfo.EsAliasIndex + "-" + appId).
Query(timeQuery).
Aggregation(aggrName, typeAggr).
Size(0).
Do(ctx)
if err != nil {
if aggrResult != nil && aggrResult.Error != nil {
errMsg, err := json.Marshal(aggrResult.Error)
if err != nil {
beego.Error(string(errMsg))
}
}
return nil, err
}
result := make([][]interface{}, 0)
if aggrResult != nil && aggrResult.Aggregations != nil {
if terms, ok := aggrResult.Aggregations.Terms(aggrName); ok && terms.Buckets != nil {
result = make([][]interface{}, len(terms.Buckets))
for index, item := range terms.Buckets {
result[index] = make([]interface{}, 2, 2)
result[index][0] = item.Key
result[index][1] = item.DocCount
}
}
}
return result, nil
}
package logs
import (
"github.com/astaxie/beego"
"time"
"rasp-cloud/conf"
)
var (
ErrorAlarmInfo = AlarmLogInfo{
EsType: "error-alarm",
EsIndex: "openrasp-error-alarm",
EsAliasIndex: "real-openrasp-error-alarm",
TtlTime: 24 * 365 * time.Hour,
AlarmBuffer: make(chan map[string]interface{}, conf.AppConfig.AlarmBufferSize),
FileLogger: initAlarmFileLogger("/openrasp-logs/error-alarm", "error.log"),
}
)
func init() {
registerAlarmInfo(&ErrorAlarmInfo)
}
func AddErrorAlarm(alarm map[string]interface{}) error {
defer func() {
if r := recover(); r != nil {
beego.Error("failed to add error alarm: ", r)
}
}()
return AddAlarmFunc(ErrorAlarmInfo.EsType, alarm)
}
//Copyright 2017-2019 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package logs
import (
"context"
"encoding/json"
"fmt"
"github.com/astaxie/beego"
"github.com/astaxie/beego/logs"
"github.com/olivere/elastic"
"os"
"path"
"rasp-cloud/es"
"rasp-cloud/tools"
"time"
"rasp-cloud/conf"
)
type AggrTimeParam struct {
AppId string `json:"app_id"`
StartTime int64 `json:"start_time"`
EndTime int64 `json:"end_time"`
Interval string `json:"interval"`
TimeZone string `json:"time_zone"`
}
type AggrFieldParam struct {
AppId string `json:"app_id"`
StartTime int64 `json:"start_time"`
EndTime int64 `json:"end_time"`
Size int `json:"size"`
}
type SearchAttackParam struct {
Page int `json:"page"`
Perpage int `json:"perpage"`
Data *struct {
Id string `json:"_id,omitempty"`
AppId string `json:"app_id,omitempty"`
StartTime int64 `json:"start_time"`
EndTime int64 `json:"end_time"`
RaspId string `json:"rasp_id,omitempty"`
HostName string `json:"server_hostname,omitempty"`
AttackSource string `json:"attack_source,omitempty"`
AttackUrl string `json:"url,omitempty"`
LocalIp string `json:"local_ip,omitempty"`
StackMd5 string `json:"stack_md5,omitempty"`
AttackType *[]string `json:"attack_type,omitempty"`
InterceptState *[]string `json:"intercept_state,omitempty"`
} `json:"data"`
}
type SearchPolicyParam struct {
Page int `json:"page"`
Perpage int `json:"perpage"`
Data *struct {
Id string `json:"_id,omitempty"`
AppId string `json:"app_id,omitempty"`
StartTime int64 `json:"start_time"`
EndTime int64 `json:"end_time"`
RaspId string `json:"rasp_id,omitempty"`
HostName string `json:"server_hostname,omitempty"`
LocalIp string `json:"local_ip,omitempty"`
PolicyId *[]string `json:"policy_id,omitempty"`
} `json:"data"`
}
type SearchErrorParam struct {
Page int `json:"page"`
Perpage int `json:"perpage"`
Data *struct {
Id string `json:"_id,omitempty"`
AppId string `json:"app_id,omitempty"`
StartTime int64 `json:"start_time"`
EndTime int64 `json:"end_time"`
RaspId string `json:"rasp_id,omitempty"`
HostName string `json:"server_hostname,omitempty"`
LocalIp string `json:"local_ip,omitempty"`
} `json:"data"`
}
type AlarmLogInfo struct {
EsType string
EsIndex string
EsAliasIndex string
TtlTime time.Duration
FileLogger *logs.BeeLogger
AlarmBuffer chan map[string]interface{}
}
var (
AddAlarmFunc func(string, map[string]interface{}) error
alarmInfos = make(map[string]*AlarmLogInfo)
)
func init() {
if conf.AppConfig.AlarmLogMode == "file" {
AddAlarmFunc = AddLogWithFile
} else if conf.AppConfig.AlarmLogMode == "es" {
startEsAlarmLogPush()
AddAlarmFunc = AddLogWithES
} else {
tools.Panic(tools.ErrCodeConfigInitFailed, "Unrecognized the value of RaspLogMode config", nil)
}
}
func registerAlarmInfo(info *AlarmLogInfo) {
alarmInfos[info.EsType] = info
es.RegisterTTL(info.TtlTime, info.EsAliasIndex+"-*")
}
func initAlarmFileLogger(dirName string, fileName string) *logs.BeeLogger {
dirName = tools.GetCurrentPathWithPanic() + dirName
if isExists, _ := tools.PathExists(dirName); !isExists {
err := os.MkdirAll(dirName, os.ModePerm)
if err != nil {
tools.Panic(tools.ErrCodeLogInitFailed, "failed to init alarm logger", err)
}
}
logger := logs.NewLogger()
logPath := path.Join(dirName, fileName)
err := logger.SetLogger(tools.AdapterAlarmFile,
`{"filename":"`+logPath+`", "daily":true, "maxdays":10, "perm":"0777"}`)
if err != nil {
tools.Panic(tools.ErrCodeLogInitFailed, "failed to init alarm logger", err)
}
return logger
}
func startEsAlarmLogPush() {
go func() {
for {
handleEsLogPush()
}
}()
}
func handleEsLogPush() {
defer func() {
if r := recover(); r != nil {
beego.Error("failed to push es alarm log: ", r)
}
}()
select {
case alarm := <-AttackAlarmInfo.AlarmBuffer:
alarms := make([]map[string]interface{}, 0, 200)
alarms = append(alarms, alarm)
for len(AttackAlarmInfo.AlarmBuffer) > 0 && len(alarms) < 200 {
alarm := <-AttackAlarmInfo.AlarmBuffer
alarms = append(alarms, alarm)
}
err := es.BulkInsert(AttackAlarmInfo.EsType, alarms)
if err != nil {
beego.Error("failed to execute es bulk insert for attack alarm: " + err.Error())
}
case alarm := <-PolicyAlarmInfo.AlarmBuffer:
alarms := make([]map[string]interface{}, 0, 200)
alarms = append(alarms, alarm)
for len(PolicyAlarmInfo.AlarmBuffer) > 0 && len(alarms) < 200 {
alarm := <-PolicyAlarmInfo.AlarmBuffer
alarms = append(alarms, alarm)
}
err := es.BulkInsert(PolicyAlarmInfo.EsType, alarms)
if err != nil {
beego.Error("failed to execute es bulk insert for policy alarm: " + err.Error())
}
case alarm := <-ErrorAlarmInfo.AlarmBuffer:
alarms := make([]map[string]interface{}, 0, 200)
alarms = append(alarms, alarm)
for len(ErrorAlarmInfo.AlarmBuffer) > 0 && len(alarms) < 200 {
alarm := <-ErrorAlarmInfo.AlarmBuffer
alarms = append(alarms, alarm)
}
err := es.BulkInsert(ErrorAlarmInfo.EsType, alarms)
if err != nil {
beego.Error("failed to execute es bulk insert for error alarm: " + err.Error())
}
}
}
func AddLogWithFile(alarmType string, alarm map[string]interface{}) error {
if info, ok := alarmInfos[alarmType]; ok && info.FileLogger != nil {
content, err := json.Marshal(alarm)
if err != nil {
return err
}
_, err = info.FileLogger.Write(content)
if err != nil {
logs.Error("failed to write rasp log: " + err.Error())
return err
}
} else {
logs.Error("failed to write rasp log, unrecognized log type: " + alarmType)
}
return nil
}
func AddLogWithES(alarmType string, alarm map[string]interface{}) error {
select {
case alarmInfos[alarmType].AlarmBuffer <- alarm:
default:
logs.Error("Failed to write attack alarm to ES, " +
"the buffer is full. Consider increase AlarmBufferSize value: " + fmt.Sprintf("%+v", alarm))
}
return nil
}
func getVulnAggr(attackTimeTopHitName string) (*elastic.TermsAggregation) {
attackMaxAggrName := "attack_max_aggr"
attackTimeTopHitAggr := elastic.NewTopHitsAggregation().
Size(1).
Sort("event_time", false).
DocvalueFields("event_time", "attack_type", "intercept_state", "url",
"path", "rasp_id", "attack_source", "plugin_algorithm", "server_ip", "server_hostname")
attackTimeMaxAggr := elastic.NewMaxAggregation().Field("event_time")
return elastic.NewTermsAggregation().
Field("stack_md5").
Size(10000).
Order(attackMaxAggrName, false).
SubAggregation(attackMaxAggrName, attackTimeMaxAggr).
SubAggregation(attackTimeTopHitName, attackTimeTopHitAggr)
}
func SearchLogs(startTime int64, endTime int64, isAttachAggr bool, query map[string]interface{}, sortField string,
page int, perpage int, ascending bool, index ...string) (int64, []map[string]interface{}, error) {
var total int64
var attackAggrName = "attack_aggr"
var attackTimeTopHitName = "attack_time_top_hit"
filterQueries := make([]elastic.Query, 0, len(query)+1)
shouldQueries := make([]elastic.Query, 0, len(query)+1)
if query != nil {
for key, value := range query {
if key == "attack_type" {
if v, ok := value.([]interface{}); ok {
filterQueries = append(filterQueries, elastic.NewTermsQuery(key, v...))
} else {
filterQueries = append(filterQueries, elastic.NewTermQuery(key, value))
}
} else if key == "intercept_state" {
if v, ok := value.([]interface{}); ok {
filterQueries = append(filterQueries, elastic.NewTermsQuery(key, v...))
} else {
filterQueries = append(filterQueries, elastic.NewTermQuery(key, value))
}
} else if key == "policy_id" {
if v, ok := value.([]interface{}); ok {
filterQueries = append(filterQueries, elastic.NewTermsQuery(key, v...))
} else {
filterQueries = append(filterQueries, elastic.NewTermQuery(key, value))
}
} else if key == "local_ip" {
filterQueries = append(filterQueries,
elastic.NewNestedQuery("server_nic", elastic.NewTermQuery("server_nic.ip", value)))
} else if key == "attack_source" {
filterQueries = append(filterQueries, elastic.NewWildcardQuery(key, "*"+fmt.Sprint(value)+"*"))
} else if key == "server_hostname" {
shouldQueries = append(shouldQueries,
elastic.NewWildcardQuery("server_hostname", "*"+fmt.Sprint(value)+"*"))
shouldQueries = append(shouldQueries,
elastic.NewNestedQuery("server_nic",
elastic.NewWildcardQuery("server_nic.ip", "*"+fmt.Sprint(value)+"*")))
} else if key == "url" {
filterQueries = append(filterQueries,
elastic.NewWildcardQuery("url", "*"+fmt.Sprint(value)+"*"))
} else {
filterQueries = append(filterQueries, elastic.NewTermQuery(key, value))
}
}
}
filterQueries = append(filterQueries, elastic.NewRangeQuery("event_time").Gte(startTime).Lte(endTime))
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(15*time.Second))
defer cancel()
boolQuery := elastic.NewBoolQuery().Filter(filterQueries...)
if len(shouldQueries) > 0 {
boolQuery.Should(shouldQueries...).MinimumNumberShouldMatch(1)
}
queryService := es.ElasticClient.Search(index...).Query(boolQuery)
if isAttachAggr {
attackAggr := getVulnAggr(attackTimeTopHitName)
queryService.Aggregation(attackAggrName, attackAggr).Size(0)
} else {
queryService.From((page - 1) * perpage).Size(perpage).Sort(sortField, ascending)
}
queryResult, err := queryService.Do(ctx)
if err != nil {
if queryResult != nil && queryResult.Error != nil {
beego.Error(queryResult.Error, index)
}
return 0, nil, err
}
result := make([]map[string]interface{}, 0)
if !isAttachAggr {
if queryResult != nil && queryResult.Hits != nil && queryResult.Hits.Hits != nil {
hits := queryResult.Hits.Hits
total = queryResult.Hits.TotalHits
result = make([]map[string]interface{}, len(hits))
for index, item := range hits {
result[index] = make(map[string]interface{})
err := json.Unmarshal(*item.Source, &result[index])
if err != nil {
return 0, nil, err
}
result[index]["id"] = item.Id
delete(result[index], "_@timestamp")
delete(result[index], "@timestamp")
delete(result[index], "@version")
delete(result[index], "tags")
delete(result[index], "host")
}
}
} else {
if queryResult != nil && queryResult.Aggregations != nil {
if terms, ok := queryResult.Aggregations.Terms(attackAggrName); ok && terms.Buckets != nil {
total = int64(len(terms.Buckets))
result = make([]map[string]interface{}, 0, perpage)
for i := 0; i < perpage; i++ {
index := i + (page-1)*perpage
if index >= int(total) {
break
}
value := make(map[string]interface{})
item := terms.Buckets[index]
if topHit, ok := item.TopHits(attackTimeTopHitName); ok &&
topHit.Hits != nil && topHit.Hits.Hits != nil {
hits := topHit.Hits.Hits
if len(hits) > 0 {
err := json.Unmarshal(*hits[0].Source, &value)
if err != nil {
return 0, nil, err
}
value["id"] = hits[0].Id
value["attack_count"] = terms.Buckets[index].DocCount
result = append(result, value)
}
}
}
}
}
}
return total, result, nil
}
func CreateAlarmEsIndex(appId string) (err error) {
for _, alarmInfo := range alarmInfos {
err = es.CreateEsIndex(alarmInfo.EsIndex + "-" + appId)
if err != nil {
return
}
}
return
}
//Copyright 2017-2019 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package logs
import (
"fmt"
"crypto/md5"
"github.com/astaxie/beego"
"time"
"rasp-cloud/conf"
)
var (
PolicyAlarmInfo = AlarmLogInfo{
EsType: "policy-alarm",
EsIndex: "openrasp-policy-alarm",
EsAliasIndex: "real-openrasp-policy-alarm",
TtlTime: 24 * 365 * time.Hour,
AlarmBuffer: make(chan map[string]interface{}, conf.AppConfig.AlarmBufferSize),
FileLogger: initAlarmFileLogger("/openrasp-logs/policy-alarm", "policy.log"),
}
)
func init() {
registerAlarmInfo(&PolicyAlarmInfo)
}
func AddPolicyAlarm(alarm map[string]interface{}) error {
defer func() {
if r := recover(); r != nil {
beego.Error("failed to add policy alarm: ", r)
}
}()
if stack, ok := alarm["stack_trace"]; ok && stack != nil && stack != "" {
_, ok = stack.(string)
if ok {
alarm["stack_md5"] = fmt.Sprintf("%x", md5.Sum([]byte(stack.(string))))
}
}
idContent := ""
idContent += fmt.Sprint(alarm["rasp_id"])
idContent += fmt.Sprint(alarm["policy_id"])
idContent += fmt.Sprint(alarm["stack_md5"])
if alarm["policy_id"] == "3007" && alarm["policy_params"] != nil {
if policyParam, ok := alarm["policy_params"].(map[string]interface{}); ok && len(policyParam) > 0 {
idContent += fmt.Sprint(policyParam["type"])
}
} else if alarm["policy_id"] == "3006" && alarm["policy_params"] != nil {
if policyParam, ok := alarm["policy_params"].(map[string]interface{}); ok && len(policyParam) > 0 {
idContent += fmt.Sprint(policyParam["connectionString"])
idContent += fmt.Sprint(policyParam["port"])
idContent += fmt.Sprint(policyParam["server"])
idContent += fmt.Sprint(policyParam["hostname"])
idContent += fmt.Sprint(policyParam["socket"])
idContent += fmt.Sprint(policyParam["username"])
}
}
alarm["upsert_id"] = fmt.Sprintf("%x", md5.Sum([]byte(idContent)))
return AddAlarmFunc(PolicyAlarmInfo.EsType, alarm)
}
//Copyright 2017-2019 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package models
import (
"rasp-cloud/mongo"
"gopkg.in/mgo.v2/bson"
"rasp-cloud/tools"
"github.com/astaxie/beego"
"gopkg.in/mgo.v2"
"strconv"
"time"
"math/rand"
"fmt"
"crypto/sha1"
)
// operations about app
type Operation struct {
Id string `json:"id" bson:"_id,omitempty"`
TypeId int `json:"type_id" bson:"type_id,omitempty"`
AppId string `json:"app_id" bson:"app_id,omitempty"`
Time int64 `json:"time" bson:"time,omitempty"`
User string `json:"user" bson:"user,omitempty"`
Content string `json:"content" bson:"content,omitempty"`
Ip string `json:"ip" bson:"ip,omitempty"`
}
const (
operationCollectionName = "operation"
OperationTypeRegisterRasp = 1001 + iota
OperationTypeDeleteRasp
OperationTypeRegenerateSecret
OperationTypeUpdateGenerateConfig
OperationTypeUpdateWhitelistConfig
OperationTypeUpdateAlgorithmConfig
OperationTypeUpdateAlarmConfig
OperationTypeSetSelectedPlugin
OperationTypeUploadPlugin
OperationTypeDeletePlugin
OperationTypeAddApp
OperationTypeDeleteApp
OperationTypeEditApp
OperationTypeRestorePlugin
)
func init() {
index := &mgo.Index{
Key: []string{"app_id"},
Unique: false,
Background: true,
Name: "app_id",
}
err := mongo.CreateIndex(operationCollectionName, index)
if err != nil {
tools.Panic(tools.ErrCodeConfigInitFailed,
"failed to create app_id index for operation collection", err)
}
index = &mgo.Index{
Key: []string{"time"},
Unique: false,
Background: true,
Name: "time",
}
err = mongo.CreateIndex(operationCollectionName, index)
if err != nil {
tools.Panic(tools.ErrCodeConfigInitFailed,
"failed to create operate_time index for operation collection", err)
}
}
func AddOperation(appId string, typeId int, ip string, content string, userName ...string) error {
var user string
var err error
if len(userName) == 0 {
user, err = GetLoginUserName()
if err != nil {
beego.Error("failed to add operation with content: " + content + ",can not get username: " + err.Error())
return err
}
} else {
user = userName[0]
}
var operation = &Operation{
AppId: appId,
TypeId: typeId,
Ip: ip,
Id: generateOperationId(),
User: user,
Time: time.Now().UnixNano() / 1000000,
Content: content,
}
err = mongo.Insert(operationCollectionName, operation)
if err != nil {
beego.Error("failed to add operation with content: " + operation.Content + ",error is: " + err.Error())
}
return err
}
func generateOperationId() string {
random := string(bson.NewObjectId()) + strconv.FormatInt(time.Now().UnixNano(), 10) +
strconv.Itoa(rand.Intn(10000))
return fmt.Sprintf("%x", sha1.Sum([]byte(random)))
}
func FindOperation(data *Operation, startTime int64, endTime int64,
page int, perpage int) (count int, result []Operation, err error) {
searchData := bson.M{}
if data.Ip != "" {
searchData["ip"] = data.Ip
}
if data.AppId != "" {
searchData["app_id"] = data.AppId
}
if data.User != "" {
searchData["user"] = data.User
}
if data.TypeId != 0 {
searchData["type_id"] = data.TypeId
}
if data.Id != "" {
searchData["_id"] = data.Id
}
searchData["time"] = bson.M{"$gte": startTime, "$lte": endTime}
count, err = mongo.FindAll(operationCollectionName, searchData, &result, perpage*(page-1), perpage, "-time")
if result == nil {
result = make([]Operation, 0)
}
return
}
//Copyright 2017-2019 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package models
import (
"fmt"
"crypto/md5"
"rasp-cloud/mongo"
"gopkg.in/mgo.v2/bson"
"sync"
"time"
"regexp"
"encoding/json"
"errors"
"strconv"
"math/rand"
"crypto/sha1"
"rasp-cloud/tools"
"gopkg.in/mgo.v2"
"bufio"
"bytes"
"github.com/robertkrimen/otto"
"rasp-cloud/conf"
)
type Plugin struct {
Id string `json:"id" bson:"_id,omitempty"`
AppId string `json:"app_id" bson:"app_id"`
Name string `json:"name" bson:"name"`
UploadTime int64 `json:"upload_time" bson:"upload_time"`
Version string `json:"version" bson:"version"`
Md5 string `json:"md5" bson:"md5"`
OriginContent string `json:"origin_content" bson:"origin_content"`
Content string `json:"plugin,omitempty" bson:"content"`
DefaultAlgorithmConfig map[string]interface{} `json:"-" bson:"default_algorithm_config"`
AlgorithmConfig map[string]interface{} `json:"algorithm_config" bson:"algorithm_config"`
}
const (
pluginCollectionName = "plugin"
)
var (
mutex sync.Mutex
)
func init() {
index := &mgo.Index{
Key: []string{"app_id"},
Unique: false,
Background: true,
Name: "app_id",
}
err := mongo.CreateIndex(pluginCollectionName, index)
if err != nil {
tools.Panic(tools.ErrCodeMongoInitFailed,
"failed to create app_id index for plugin collection", err)
}
index = &mgo.Index{
Key: []string{"upload_time"},
Unique: false,
Background: true,
Name: "upload_time",
}
err = mongo.CreateIndex(pluginCollectionName, index)
if err != nil {
tools.Panic(tools.ErrCodeMongoInitFailed,
"failed to create upload_time index for plugin collection", err)
}
}
func AddPlugin(pluginContent []byte, appId string) (plugin *Plugin, err error) {
plugin, err = generatePlugin(pluginContent, appId)
if err != nil {
return
}
err = addPluginToDb(plugin)
if err != nil {
return nil, err
}
return
}
func generatePlugin(pluginContent []byte, appId string) (plugin *Plugin, err error) {
pluginReader := bufio.NewReader(bytes.NewReader(pluginContent))
firstLine, err := pluginReader.ReadString('\n')
if err != nil {
return nil, errors.New("failed to read the plugin file: " + err.Error())
}
secondLine, err := pluginReader.ReadString('\n')
if err != nil {
return nil, errors.New("failed to read the plugin file: " + err.Error())
}
var newVersion string
var newPluginName string
if newVersion = regexp.MustCompile(`'.+'|".+"`).FindString(firstLine); newVersion == "" {
return nil, errors.New("failed to find the plugin version")
}
newVersion = newVersion[1 : len(newVersion)-1]
if newPluginName = regexp.MustCompile(`'.+'|".+"`).FindString(secondLine); newPluginName == "" {
return nil, errors.New("failed to find the plugin name")
}
newPluginName = newPluginName[1 : len(newPluginName)-1]
algorithmStartMsg := "// BEGIN ALGORITHM CONFIG //"
algorithmEndMsg := "// END ALGORITHM CONFIG //"
algorithmStart := bytes.Index(pluginContent, []byte(algorithmStartMsg))
if algorithmStart < 0 {
return nil, errors.New("failed to find the start of algorithmConfig variable: " + algorithmStartMsg)
}
algorithmStart = algorithmStart + len([]byte(algorithmStartMsg))
algorithmEnd := bytes.Index(pluginContent, []byte(algorithmEndMsg))
if algorithmEnd < 0 {
return nil, errors.New("failed to find the end of algorithmConfig variable: " + algorithmEndMsg)
}
jsVm := otto.New()
_, err = jsVm.Run(string(pluginContent[algorithmStart:algorithmEnd]) +
"\n algorithmContent=JSON.stringify(algorithmConfig)")
if err != nil {
return nil, errors.New("failed to get algorithm config from plugin: " + err.Error())
}
algorithmContent, err := jsVm.Get("algorithmContent")
if err != nil {
return nil, errors.New("failed to get algorithm config from plugin: " + err.Error())
}
var algorithmData map[string]interface{}
err = json.Unmarshal([]byte(algorithmContent.String()), &algorithmData)
if err != nil {
return nil, errors.New("failed to unmarshal algorithm json data: " + err.Error())
}
newMd5 := fmt.Sprintf("%x", md5.Sum(pluginContent))
plugin = &Plugin{
Id: generatePluginId(appId),
Version: newVersion,
Name: newPluginName,
Md5: newMd5,
OriginContent: string(pluginContent),
Content: string(pluginContent),
UploadTime: time.Now().UnixNano() / 1000000,
AppId: appId,
DefaultAlgorithmConfig: algorithmData,
AlgorithmConfig: algorithmData,
}
return plugin, nil
}
func addPluginToDb(plugin *Plugin) (err error) {
mutex.Lock()
defer mutex.Unlock()
var count int
if conf.AppConfig.MaxPlugins > 0 {
_, oldPlugins, err := GetPluginsByApp(plugin.AppId, conf.AppConfig.MaxPlugins-1, 0)
if err != nil {
return err
}
count = len(oldPlugins)
if count > 0 {
for _, oldPlugin := range oldPlugins {
app := &App{}
err = mongo.FindOne("app", bson.M{"selected_plugin_id": oldPlugin.Id}, app)
if err != nil && err != mgo.ErrNotFound {
return err
}
if app != nil {
continue
}
err = mongo.RemoveId(pluginCollectionName, oldPlugin.Id)
if err != nil {
return err
}
}
}
}
err = mongo.Insert(pluginCollectionName, plugin)
return
}
func generatePluginId(appId string) string {
random := string(bson.NewObjectId()) + appId +
strconv.FormatInt(time.Now().UnixNano(), 10) + strconv.Itoa(rand.Intn(10000))
return fmt.Sprintf("%x", sha1.Sum([]byte(random)))
}
func GetSelectedPlugin(appId string, hasContent bool) (plugin *Plugin, err error) {
var app *App
if err = mongo.FindId(appCollectionName, appId, &app); err != nil {
return nil, errors.New("can not get app," + err.Error())
}
return GetPluginById(app.SelectedPluginId, hasContent)
}
func SetSelectedPlugin(appId string, pluginId string) (plugin *Plugin, err error) {
plugin, err = GetPluginById(pluginId, false)
if err != nil {
return
}
return plugin, mongo.UpdateId(appCollectionName, appId, bson.M{"selected_plugin_id": pluginId})
}
func RestoreDefaultConfiguration(pluginId string) (appId string, err error) {
plugin, err := GetPluginById(pluginId, true)
if err != nil {
return "", err
}
return handleAlgorithmConfig(plugin, plugin.DefaultAlgorithmConfig)
}
func UpdateAlgorithmConfig(pluginId string, config map[string]interface{}) (appId string, err error) {
plugin, err := GetPluginById(pluginId, true)
if err != nil {
return "", err
}
if err := validAlgorithmConfig(plugin, config); err != nil {
return "", err
}
return handleAlgorithmConfig(plugin, config)
}
func validAlgorithmConfig(plugin *Plugin, config map[string]interface{}) error {
errMsg := "failed to match the new config format to default algorithm config format"
for key, defaultValue := range plugin.DefaultAlgorithmConfig {
if c, ok := config[key]; !ok || (c == nil && defaultValue != nil) {
return errors.New(errMsg + ", " + "can not find the key '" + key + "' in new config")
}
if defaultValue != nil {
if defaultItem, ok := defaultValue.(map[string]interface{}); ok {
if item, ok := config[key].(map[string]interface{}); ok {
for subKey := range defaultItem {
if _, ok := item[subKey]; !ok {
return errors.New(errMsg + ", " + "can not find the key '" +
key + "." + subKey + "' in new config")
}
}
} else {
return errors.New(errMsg + ", " + "the key '" + key + "' must be an object")
}
}
}
}
return nil
}
func handleAlgorithmConfig(plugin *Plugin, config map[string]interface{}) (appId string, err error) {
content, err := json.MarshalIndent(config, "", "\t")
if err != nil {
return "", err
}
regex := `//\s*BEGIN\s*ALGORITHM\s*CONFIG\s*//[\W\w]*?//\s*END\s*ALGORITHM\s*CONFIG\s*//`
newContent := "// BEGIN ALGORITHM CONFIG //\n\n" +
"var algorithmConfig = " +
string(content) + "\n\n// END ALGORITHM CONFIG //"
if variable := regexp.MustCompile(regex).
FindString(plugin.Content); len(variable) <= 0 {
return "", errors.New("failed to find algorithmConfig variable")
}
algorithmContent := regexp.MustCompile(regex).ReplaceAllString(plugin.Content, newContent)
newMd5 := fmt.Sprintf("%x", md5.Sum([]byte(algorithmContent)))
return plugin.AppId, mongo.UpdateId(pluginCollectionName, plugin.Id, bson.M{"content": algorithmContent,
"algorithm_config": config, "md5": newMd5})
}
func GetPluginById(id string, hasContent bool) (plugin *Plugin, err error) {
newSession := mongo.NewSession()
defer newSession.Close()
query := newSession.DB(mongo.DbName).C(pluginCollectionName).FindId(id)
if hasContent {
err = query.One(&plugin)
} else {
err = query.Select(bson.M{"content": 0}).One(&plugin)
}
return
}
func GetPluginsByApp(appId string, skip int, limit int) (total int, plugins []Plugin, err error) {
newSession := mongo.NewSession()
defer newSession.Close()
total, err = newSession.DB(mongo.DbName).C(pluginCollectionName).Find(bson.M{"app_id": appId}).Count()
if err != nil {
return
}
err = newSession.DB(mongo.DbName).C(pluginCollectionName).Find(bson.M{"app_id": appId}).Select(bson.M{"content": 0}).
Sort("-upload_time").Skip(skip).Limit(limit).All(&plugins)
if plugins == nil {
plugins = make([]Plugin, 0)
}
return
}
func DeletePlugin(pluginId string) error {
mutex.Lock()
defer mutex.Unlock()
return mongo.RemoveId(pluginCollectionName, pluginId)
}
func RemovePluginByAppId(appId string) error {
_, err := mongo.RemoveAll(pluginCollectionName, bson.M{"app_id": appId})
return err
}
//Copyright 2017-2019 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package models
import (
"rasp-cloud/mongo"
"rasp-cloud/tools"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
"time"
"strconv"
"errors"
"github.com/astaxie/beego/httplib"
)
type Rasp struct {
Id string `json:"id" bson:"_id,omitempty"`
AppId string `json:"app_id" bson:"app_id,omitempty"`
Version string `json:"version" bson:"version,omitempty"`
HostName string `json:"hostname" bson:"hostname,omitempty"`
RegisterIp string `json:"register_ip" bson:"register_ip,omitempty"`
Language string `json:"language" bson:"language,omitempty"`
LanguageVersion string `json:"language_version" bson:"language_version,omitempty"`
ServerType string `json:"server_type" bson:"server_type,omitempty"`
ServerVersion string `json:"server_version" bson:"server_version,omitempty"`
RaspHome string `json:"rasp_home" bson:"rasp_home,omitempty"`
PluginVersion string `json:"plugin_version" bson:"plugin_version,omitempty"`
HeartbeatInterval int64 `json:"heartbeat_interval" bson:"heartbeat_interval,omitempty"`
Online *bool `json:"online" bson:"online,omitempty"`
LastHeartbeatTime int64 `json:"last_heartbeat_time" bson:"last_heartbeat_time,omitempty"`
RegisterTime int64 `json:"register_time" bson:"register_time,omitempty"`
Environ map[string]string `json:"environ" bson:"environ,omitempty"`
}
const (
raspCollectionName = "rasp"
)
func init() {
index := &mgo.Index{
Key: []string{"app_id"},
Unique: false,
Background: true,
Name: "app_id",
}
err := mongo.CreateIndex(raspCollectionName, index)
if err != nil {
tools.Panic(tools.ErrCodeMongoInitFailed,
"failed to create app_id index for rasp collection", err)
}
index = &mgo.Index{
Key: []string{"register_time"},
Unique: false,
Background: true,
Name: "register_time",
}
err = mongo.CreateIndex(raspCollectionName, index)
if err != nil {
tools.Panic(tools.ErrCodeMongoInitFailed,
"failed to create register_time index for rasp collection", err)
}
}
func UpsertRaspById(id string, rasp *Rasp) (error) {
return mongo.UpsertId(raspCollectionName, id, rasp)
}
func GetRaspByAppId(id string, page int, perpage int) (count int, result []*Rasp, err error) {
count, err = mongo.FindAll(raspCollectionName, bson.M{"app_id": id}, &result, perpage*(page-1), perpage)
if err == nil {
for _, rasp := range result {
HandleRasp(rasp)
}
}
return
}
func RemoveRaspByAppId(appId string) (err error) {
_, err = mongo.RemoveAll(raspCollectionName, bson.M{"app_id": appId})
return
}
func FindRasp(selector *Rasp, page int, perpage int) (count int, result []*Rasp, err error) {
var bsonContent []byte
bsonContent, err = bson.Marshal(selector)
if err != nil {
return
}
bsonModel := bson.M{}
err = bson.Unmarshal(bsonContent, &bsonModel)
if err != nil {
return
}
if bsonModel["hostname"] != nil {
bsonModel["$or"] = []bson.M{
{
"hostname": bson.M{
"$regex": bsonModel["hostname"],
"$options": "$i",
},
},
{
"register_ip": bson.M{
"$regex": bsonModel["hostname"],
"$options": "$i",
},
},
}
delete(bsonModel, "hostname")
}
if selector.Online != nil {
delete(bsonModel, "online")
if *selector.Online {
bsonModel["$where"] = "this.last_heartbeat_time+this.heartbeat_interval+180 >= " +
strconv.FormatInt(time.Now().Unix(), 10)
} else {
bsonModel["$where"] = "this.last_heartbeat_time+this.heartbeat_interval+180 < " +
strconv.FormatInt(time.Now().Unix(), 10)
}
}
count, err = mongo.FindAllBySort(raspCollectionName, bsonModel, perpage*(page-1), perpage,
&result, "-register_time")
if err == nil {
for _, rasp := range result {
if selector.Online != nil {
rasp.Online = selector.Online
} else {
HandleRasp(rasp)
}
}
}
return
}
func GetRaspById(id string) (rasp *Rasp, err error) {
err = mongo.FindId(raspCollectionName, id, &rasp)
if err == nil {
HandleRasp(rasp)
}
return
}
func HandleRasp(rasp *Rasp) {
var online bool
heartbeatInterval := rasp.HeartbeatInterval + 180
if time.Now().Unix()-rasp.LastHeartbeatTime > heartbeatInterval {
online = false
} else {
online = true
}
rasp.Online = &online
if rasp.Environ == nil {
rasp.Environ = map[string]string{}
}
}
func RemoveRaspById(id string) (err error) {
rasp, err := GetRaspById(id)
if err != nil {
return err
}
if *rasp.Online {
return errors.New("unable to delete online rasp")
}
return mongo.RemoveId(raspCollectionName, id)
}
func RemoveRaspBySelector(selector map[string]interface{}, appId string) (int, error) {
offlineWhere := ""
if _, ok := selector["expire_time"]; ok {
expireTime := strconv.FormatInt(selector["expire_time"].(int64), 10)
offlineWhere = "this.last_heartbeat_time+this.heartbeat_interval+180+" + expireTime + "<" +
strconv.FormatInt(time.Now().Unix(), 10)
} else {
offlineWhere = "this.last_heartbeat_time+this.heartbeat_interval+180 < " +
strconv.FormatInt(time.Now().Unix(), 10)
}
param := bson.M{"app_id": appId, "$where": offlineWhere}
if selector["register_ip"] != nil && selector["register_ip"] != "" {
param["register_ip"] = selector["register_ip"]
}
info, err := mongo.RemoveAll(raspCollectionName, param)
if err != nil {
return 0, err
}
return info.Removed, nil
}
func RegisterCallback(url string, token string, rasp *Rasp) error {
var resBody struct {
Msg string `json:"message"`
}
request, err := httplib.Post(url).
JSONBody(rasp)
if err != nil {
return err
}
response, err := request.Header("openrasp-token", token).
SetTimeout(10*time.Second, 10*time.Second).
Response()
if err != nil {
return err
}
if response.StatusCode != 200 {
return errors.New("the status code is error: " + response.Status)
}
err = request.ToJSON(&resBody)
if err != nil {
return errors.New("response body is invalid: " + err.Error())
}
if resBody.Msg != "ok" {
return errors.New("the message of response body is not ok: " + resBody.Msg)
}
return nil
}
//Copyright 2017-2019 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package models
import (
"rasp-cloud/es"
"time"
"github.com/olivere/elastic"
"context"
)
type ReportData struct {
RaspId string `json:"rasp_id"`
Time int64 `json:"time"`
RequestSum int64 `json:"request_sum"`
InsertTime int64 `json:"@timestamp"`
}
var (
ReportIndexName = "openrasp-report-data"
AliasReportIndexName = "real-openrasp-report-data"
reportType = "report-data"
)
func init() {
es.RegisterTTL(24*100*time.Hour, AliasReportIndexName+"-*")
}
func CreateReportDataEsIndex(appId string) error {
return es.CreateEsIndex(ReportIndexName + "-" + appId)
}
func AddReportData(reportData *ReportData, appId string) error {
reportData.InsertTime = time.Now().Unix() * 1000
return es.Insert(AliasReportIndexName+"-"+appId, reportType, reportData)
}
func GetHistoryRequestSum(startTime int64, endTime int64, interval string, timeZone string,
appId string) (error, []map[string]interface{}) {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(10*time.Second))
defer cancel()
timeAggrName := "aggr_time"
sumAggrName := "request_sum"
timeAggr := elastic.NewDateHistogramAggregation().Field("time").TimeZone(timeZone).
Interval(interval).ExtendedBounds(startTime, endTime)
requestSumAggr := elastic.NewSumAggregation().Field("request_sum")
timeAggr.SubAggregation(sumAggrName, requestSumAggr)
timeQuery := elastic.NewRangeQuery("time").Gte(startTime).Lte(endTime)
aggrResult, err := es.ElasticClient.Search(AliasReportIndexName + "-" + appId).
Query(timeQuery).
Aggregation(timeAggrName, timeAggr).
Size(0).
Do(ctx)
if err != nil {
return err, nil
}
result := make([]map[string]interface{}, 0)
if aggrResult != nil && aggrResult.Aggregations != nil {
if terms, ok := aggrResult.Aggregations.Terms(timeAggrName); ok && terms.Buckets != nil {
result = make([]map[string]interface{}, len(terms.Buckets))
for index, item := range terms.Buckets {
result[index] = make(map[string]interface{})
result[index]["start_time"] = item.Key
if sumItem, ok := item.Sum(sumAggrName); ok {
result[index]["request_sum"] = sumItem.Value
} else {
result[index]["request_sum"] = 0
}
}
}
}
return nil, result
}
//Copyright 2017-2019 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package models
import "rasp-cloud/mongo"
const (
serverUrlId = "0"
serverUrlCollectionName = "server_url"
serverAgentCollectionName = "server_agent"
)
type ServerUrl struct {
PanelUrl string `json:"panel_url" bson:"panel_url"`
AgentUrls []string `json:"agent_urls" bson:"agent_urls"`
}
func GetServerUrl() (serverUrl *ServerUrl, err error) {
err = mongo.FindId(serverUrlCollectionName, serverUrlId, &serverUrl)
if err == nil && serverUrl.AgentUrls == nil {
serverUrl.AgentUrls = []string{}
}
return
}
func PutServerUrl(serverUrl *ServerUrl) (error) {
return mongo.UpsertId(serverUrlCollectionName, serverUrlId, &serverUrl)
}
//Copyright 2017-2019 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package models
import (
"rasp-cloud/mongo"
)
type Token struct {
Token string `json:"token" bson:"_id"`
Description string `json:"description" bson:"description"`
}
const (
tokenCollectionName = "token"
AuthTokenName = "X-OpenRASP-Token"
)
func GetAllToken(page int, perpage int) (count int, result []*Token, err error) {
count, err = mongo.FindAll(tokenCollectionName, nil, &result, perpage*(page-1), perpage)
return
}
func HasToken(token string) (bool, error) {
var result *Token
err := mongo.FindId(tokenCollectionName, token, &result)
if err != nil || result == nil {
return false, err
}
return true, err
}
func AddToken(token *Token) (result *Token, err error) {
if token.Token == "" {
token.Token = generateOperationId()
}
err = mongo.UpsertId(tokenCollectionName, token.Token, token)
result = token
return
}
func RemoveToken(tokenId string) (token *Token, err error) {
err = mongo.FindId(tokenCollectionName, tokenId, &token)
if err != nil {
return
}
return token, mongo.RemoveId(tokenCollectionName, tokenId)
}
//Copyright 2017-2019 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package models
import (
"github.com/astaxie/beego"
"github.com/astaxie/beego/logs"
"github.com/pkg/errors"
"golang.org/x/crypto/bcrypt"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
"os"
"rasp-cloud/mongo"
"rasp-cloud/tools"
"regexp"
"rasp-cloud/conf"
)
const (
userCollectionName = "user"
userName = "openrasp"
)
type User struct {
Id string `bson:"_id"`
Name string `json:"name" bson:"name"`
Password string `json:"password" bson:"password"`
}
var (
userId string
)
func init() {
count, err := mongo.Count(userCollectionName)
if err != nil {
tools.Panic(tools.ErrCodeMongoInitFailed, "failed to get the count of user collection", err)
}
index := &mgo.Index{
Key: []string{"name"},
Unique: true,
Background: true,
Name: "name",
}
err = mongo.CreateIndex(userCollectionName, index)
if err != nil {
tools.Panic(tools.ErrCodeMongoInitFailed, "failed to create name index for user collection", err)
}
if count <= 0 {
hash, err := generateHashedPassword("admin@123")
if err != nil {
tools.Panic(tools.ErrCodeGeneratePasswdFailed, "failed to generate the default hashed password", err)
}
userId = mongo.GenerateObjectId()
user := User{
Id: userId,
Name: userName,
Password: hash,
}
err = mongo.Insert(userCollectionName, user)
if err != nil {
tools.Panic(tools.ErrCodeMongoInitFailed, "failed to create default user", err)
}
} else {
var user *User
err = mongo.FindOne(userCollectionName, bson.M{}, &user)
if err != nil {
tools.Panic(tools.ErrCodeMongoInitFailed, "failed to get admin user", err)
}
userId = user.Id
}
if *conf.AppConfig.Flag.StartType == conf.StartTypeReset {
if *conf.AppConfig.Flag.Password == "" {
tools.Panic(tools.ErrCodeResetUserFailed, "the password can not be empty", err)
}
err := ResetUser(*conf.AppConfig.Flag.Password)
if err != nil {
tools.Panic(tools.ErrCodeResetUserFailed, "failed to reset administrator", err)
}
beego.Info("reset the administrator password successfully")
os.Exit(0)
}
}
func ResetUser(newPwd string) error {
err := validPassword(newPwd)
if err != nil {
return errors.New("invalid password: " + err.Error())
}
pwd, err := generateHashedPassword(newPwd)
if err != nil {
return errors.New("failed to generate password: " + err.Error())
}
err = mongo.UpdateId(userCollectionName, userId, bson.M{"password": pwd, "name": userName})
return err
}
func generateHashedPassword(password string) (string, error) {
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
beego.Error("failed to generate hashed password: " + err.Error())
return "", err
}
return string(hash), nil
}
func ComparePassword(hashedPassword string, password string) error {
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
if err != nil && err != bcrypt.ErrMismatchedHashAndPassword {
logs.Error("CompareHashAndPassword function error: " + err.Error())
}
return err
}
func validPassword(password string) error {
hasNum := regexp.MustCompile(".*[0-9].*").Match([]byte(password))
hasLetter := regexp.MustCompile(".*([a-z]|[A-Z]).*").Match([]byte(password))
if len(password) < 8 || len(password) > 50 || !hasNum || !hasLetter {
return errors.New("password must contain both letters and numbers, and the length of password must be between [8, 50]")
}
return nil
}
func GetLoginUserName() (userName string, err error) {
var user *User
err = mongo.FindId(userCollectionName, userId, &user)
if err != nil {
return
}
return user.Name, nil
}
//func GetHashedLoginPassword() (pwd string, err error) {
// var user *User
// err = mongo.FindId(userCollectionName, userId, &user)
// if err != nil {
// return
// }
// return user.Password, nil
//}
func VerifyUser(userName string, pwd string) error {
var user *User
err := mongo.FindId(userCollectionName, userId, &user)
if err != nil {
return err
}
if userName != user.Name {
return errors.New("username is incorrect")
}
return ComparePassword(user.Password, pwd)
}
func UpdatePassword(oldPwd string, newPwd string) error {
err := VerifyUser(userName, oldPwd)
if err != nil {
return errors.New("old password is incorrect")
}
err = validPassword(newPwd)
if err != nil {
return errors.New("Password does not meet complexity requirements: " + err.Error())
}
pwd, err := generateHashedPassword(newPwd)
if err != nil {
return errors.New("failed to update new password")
}
err = mongo.UpdateId(userCollectionName, userId, bson.M{"password": pwd})
return err
}
//Copyright 2017-2019 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package mongo
import (
"gopkg.in/mgo.v2"
"time"
"github.com/astaxie/beego"
"rasp-cloud/tools"
"gopkg.in/mgo.v2/bson"
"strconv"
"math/rand"
"fmt"
"crypto/sha1"
"strings"
"rasp-cloud/conf"
)
var (
minMongoVersion = "3.6.0"
session *mgo.Session
DbName = conf.AppConfig.MongoDBName
)
func init() {
var err error
dialInfo := &mgo.DialInfo{
Addrs: []string{conf.AppConfig.MongoDBAddr},
Username: conf.AppConfig.MongoDBUser,
Password: conf.AppConfig.MongoDBPwd,
Direct: false,
Timeout: time.Second * 20,
FailFast: true,
PoolLimit: conf.AppConfig.MongoDBPoolLimit,
Database: DbName,
}
session, err = mgo.DialWithInfo(dialInfo)
if err != nil {
tools.Panic(tools.ErrCodeMongoInitFailed, "failed to find MongoDB server: ", err)
}
info, err := session.BuildInfo()
if err != nil {
tools.Panic(tools.ErrCodeMongoInitFailed, "failed to get MongoDB version", err)
}
beego.Info("MongoDB version: " + info.Version)
if strings.Compare(info.Version, minMongoVersion) < 0 {
tools.Panic(tools.ErrCodeMongoInitFailed, "unable to support the MongoDB with a version lower than "+
minMongoVersion+ ","+ " the current version is "+ info.Version, nil)
}
if err != nil {
tools.Panic(tools.ErrCodeMongoInitFailed, "init MongoDB failed", err)
}
session.SetMode(mgo.Strong, true)
}
func NewSession() *mgo.Session {
return session.Copy()
}
func Count(collection string) (int, error) {
newSession := NewSession()
defer newSession.Close()
return newSession.DB(DbName).C(collection).Count()
}
func CreateIndex(collection string, index *mgo.Index) error {
newSession := NewSession()
defer newSession.Close()
return newSession.DB(DbName).C(collection).EnsureIndex(*index)
}
func Insert(collection string, doc interface{}) error {
newSession := NewSession()
defer newSession.Close()
return newSession.DB(DbName).C(collection).Insert(doc)
}
func UpsertId(collection string, id interface{}, doc interface{}) error {
newSession := NewSession()
defer newSession.Close()
_, err := newSession.DB(DbName).C(collection).UpsertId(id, doc)
return err
}
func FindAll(collection string, query interface{}, result interface{}, skip int, limit int,
sortFields ...string) (count int, err error) {
newSession := NewSession()
defer newSession.Close()
count, err = newSession.DB(DbName).C(collection).Find(query).Count()
if err != nil {
return
}
err = newSession.DB(DbName).C(collection).Find(query).Skip(skip).Limit(limit).Sort(sortFields...).All(result)
return
}
func FindAllWithoutLimit(collection string, query interface{}, result interface{},
sortFields ...string) (count int, err error) {
newSession := NewSession()
defer newSession.Close()
count, err = newSession.DB(DbName).C(collection).Find(query).Count()
if err != nil {
return
}
err = newSession.DB(DbName).C(collection).Find(query).Sort(sortFields...).All(result)
return
}
func FindAllWithSelect(collection string, query interface{}, result interface{}, selector interface{},
skip int, limit int) (count int, err error) {
newSession := NewSession()
defer newSession.Close()
count, err = newSession.DB(DbName).C(collection).Find(query).Count()
if err != nil {
return
}
err = newSession.DB(DbName).C(collection).Find(query).Select(selector).Skip(skip).Limit(limit).All(result)
return
}
func FindId(collection string, id string, result interface{}) error {
newSession := NewSession()
defer newSession.Close()
return newSession.DB(DbName).C(collection).FindId(id).One(result)
}
func FindOne(collection string, query interface{}, result interface{}) error {
newSession := NewSession()
defer newSession.Close()
return newSession.DB(DbName).C(collection).Find(query).One(result)
}
func FindAllBySort(collection string, query interface{}, skip int, limit int, result interface{},
sortFields ...string) (count int, err error) {
newSession := NewSession()
defer newSession.Close()
count, err = newSession.DB(DbName).C(collection).Find(query).Count()
if err != nil {
return
}
return count, newSession.DB(DbName).C(collection).Find(query).Sort(sortFields...).Skip(skip).Limit(limit).All(result)
}
func UpdateId(collection string, id interface{}, doc interface{}) error {
newSession := NewSession()
defer newSession.Close()
return newSession.DB(DbName).C(collection).UpdateId(id, bson.M{"$set": doc})
}
func RemoveId(collection string, id interface{}) error {
newSession := NewSession()
defer newSession.Close()
return newSession.DB(DbName).C(collection).RemoveId(id)
}
func RemoveAll(collection string, selector interface{}) (*mgo.ChangeInfo, error) {
newSession := NewSession()
defer newSession.Close()
return newSession.DB(DbName).C(collection).RemoveAll(selector)
}
func GenerateObjectId() string {
random := string(bson.NewObjectId()) +
strconv.FormatInt(time.Now().UnixNano(), 10) + strconv.Itoa(rand.Intn(10000))
return fmt.Sprintf("%x", sha1.Sum([]byte(random)))
}
package routers
import (
"github.com/astaxie/beego"
"github.com/astaxie/beego/context/param"
)
func init() {
beego.GlobalControllerRouter["rasp-cloud/controllers:PingController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers:PingController"],
beego.ControllerComments{
Method: "Ping",
Router: `/`,
AllowHTTPMethods: []string{"get","post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
}
package routers
import (
"github.com/astaxie/beego"
"github.com/astaxie/beego/context/param"
)
func init() {
beego.GlobalControllerRouter["rasp-cloud/controllers/agent:HeartbeatController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/agent:HeartbeatController"],
beego.ControllerComments{
Method: "Post",
Router: `/`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/agent:RaspController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/agent:RaspController"],
beego.ControllerComments{
Method: "Post",
Router: `/`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/agent:ReportController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/agent:ReportController"],
beego.ControllerComments{
Method: "Post",
Router: `/`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
}
package routers
import (
"github.com/astaxie/beego"
"github.com/astaxie/beego/context/param"
)
func init() {
beego.GlobalControllerRouter["rasp-cloud/controllers/agent/agent_logs:AttackAlarmController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/agent/agent_logs:AttackAlarmController"],
beego.ControllerComments{
Method: "Post",
Router: `/`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/agent/agent_logs:ErrorController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/agent/agent_logs:ErrorController"],
beego.ControllerComments{
Method: "Post",
Router: `/`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/agent/agent_logs:PolicyAlarmController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/agent/agent_logs:PolicyAlarmController"],
beego.ControllerComments{
Method: "Post",
Router: `/`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
}
package routers
import (
"github.com/astaxie/beego"
"github.com/astaxie/beego/context/param"
)
func init() {
beego.GlobalControllerRouter["rasp-cloud/controllers/api:AppController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api:AppController"],
beego.ControllerComments{
Method: "Post",
Router: `/`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api:AppController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api:AppController"],
beego.ControllerComments{
Method: "ConfigAlarm",
Router: `/alarm/config`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api:AppController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api:AppController"],
beego.ControllerComments{
Method: "ConfigApp",
Router: `/config`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api:AppController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api:AppController"],
beego.ControllerComments{
Method: "Delete",
Router: `/delete`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api:AppController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api:AppController"],
beego.ControllerComments{
Method: "TestDing",
Router: `/ding/test`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(
param.New("config"),
),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api:AppController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api:AppController"],
beego.ControllerComments{
Method: "TestEmail",
Router: `/email/test`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api:AppController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api:AppController"],
beego.ControllerComments{
Method: "UpdateAppGeneralConfig",
Router: `/general/config`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api:AppController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api:AppController"],
beego.ControllerComments{
Method: "GetApp",
Router: `/get`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api:AppController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api:AppController"],
beego.ControllerComments{
Method: "TestHttp",
Router: `/http/test`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(
param.New("config"),
),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api:AppController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api:AppController"],
beego.ControllerComments{
Method: "GetPlugins",
Router: `/plugin/get`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api:AppController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api:AppController"],
beego.ControllerComments{
Method: "SetSelectedPlugin",
Router: `/plugin/select`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api:AppController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api:AppController"],
beego.ControllerComments{
Method: "GetSelectedPlugin",
Router: `/plugin/select/get`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api:AppController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api:AppController"],
beego.ControllerComments{
Method: "GetRasps",
Router: `/rasp/get`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api:AppController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api:AppController"],
beego.ControllerComments{
Method: "GetAppSecret",
Router: `/secret/get`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api:AppController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api:AppController"],
beego.ControllerComments{
Method: "RegenerateAppSecret",
Router: `/secret/regenerate`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api:AppController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api:AppController"],
beego.ControllerComments{
Method: "UpdateAppWhiteListConfig",
Router: `/whitelist/config`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api:OperationController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api:OperationController"],
beego.ControllerComments{
Method: "Search",
Router: `/search`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api:PluginController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api:PluginController"],
beego.ControllerComments{
Method: "Upload",
Router: `/`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api:PluginController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api:PluginController"],
beego.ControllerComments{
Method: "UpdateAppAlgorithmConfig",
Router: `/algorithm/config`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api:PluginController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api:PluginController"],
beego.ControllerComments{
Method: "RestoreAlgorithmConfig",
Router: `/algorithm/restore`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api:PluginController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api:PluginController"],
beego.ControllerComments{
Method: "Delete",
Router: `/delete`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api:PluginController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api:PluginController"],
beego.ControllerComments{
Method: "Download",
Router: `/download`,
AllowHTTPMethods: []string{"get"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api:PluginController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api:PluginController"],
beego.ControllerComments{
Method: "Get",
Router: `/get`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api:RaspController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api:RaspController"],
beego.ControllerComments{
Method: "Delete",
Router: `/delete`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api:RaspController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api:RaspController"],
beego.ControllerComments{
Method: "Search",
Router: `/search`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api:ReportController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api:ReportController"],
beego.ControllerComments{
Method: "Search",
Router: `/dashboard`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api:ServerController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api:ServerController"],
beego.ControllerComments{
Method: "PutUrl",
Router: `/url`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api:ServerController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api:ServerController"],
beego.ControllerComments{
Method: "GetUrl",
Router: `/url/get`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api:TokenController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api:TokenController"],
beego.ControllerComments{
Method: "Post",
Router: `/`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api:TokenController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api:TokenController"],
beego.ControllerComments{
Method: "Delete",
Router: `/delete`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api:TokenController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api:TokenController"],
beego.ControllerComments{
Method: "Get",
Router: `/get`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api:UserController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api:UserController"],
beego.ControllerComments{
Method: "IsLogin",
Router: `/islogin`,
AllowHTTPMethods: []string{"get","post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api:UserController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api:UserController"],
beego.ControllerComments{
Method: "Login",
Router: `/login`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api:UserController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api:UserController"],
beego.ControllerComments{
Method: "Logout",
Router: `/logout`,
AllowHTTPMethods: []string{"get","post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api:UserController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api:UserController"],
beego.ControllerComments{
Method: "Update",
Router: `/update`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
}
package routers
import (
"github.com/astaxie/beego"
"github.com/astaxie/beego/context/param"
)
func init() {
beego.GlobalControllerRouter["rasp-cloud/controllers/api/fore_logs:AttackAlarmController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api/fore_logs:AttackAlarmController"],
beego.ControllerComments{
Method: "AggregationWithTime",
Router: `/aggr/time`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api/fore_logs:AttackAlarmController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api/fore_logs:AttackAlarmController"],
beego.ControllerComments{
Method: "AggregationWithType",
Router: `/aggr/type`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api/fore_logs:AttackAlarmController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api/fore_logs:AttackAlarmController"],
beego.ControllerComments{
Method: "AggregationWithUserAgent",
Router: `/aggr/ua`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api/fore_logs:AttackAlarmController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api/fore_logs:AttackAlarmController"],
beego.ControllerComments{
Method: "AggregationVuln",
Router: `/aggr/vuln`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api/fore_logs:AttackAlarmController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api/fore_logs:AttackAlarmController"],
beego.ControllerComments{
Method: "Search",
Router: `/search`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api/fore_logs:ErrorController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api/fore_logs:ErrorController"],
beego.ControllerComments{
Method: "Search",
Router: `/search`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["rasp-cloud/controllers/api/fore_logs:PolicyAlarmController"] = append(beego.GlobalControllerRouter["rasp-cloud/controllers/api/fore_logs:PolicyAlarmController"],
beego.ControllerComments{
Method: "Search",
Router: `/search`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
}
//Copyright 2017-2019 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package routers
import (
"github.com/astaxie/beego"
"rasp-cloud/controllers/agent"
"rasp-cloud/controllers/agent/agent_logs"
"rasp-cloud/controllers/api"
"rasp-cloud/controllers/api/fore_logs"
"rasp-cloud/tools"
"rasp-cloud/conf"
"rasp-cloud/controllers"
)
func InitRouter() {
agentNS := beego.NewNamespace("/agent",
beego.NSNamespace("/heartbeat",
beego.NSInclude(
&agent.HeartbeatController{},
),
),
beego.NSNamespace("/log",
beego.NSNamespace("/attack",
beego.NSInclude(
&agent_logs.AttackAlarmController{},
),
),
beego.NSNamespace("/policy",
beego.NSInclude(
&agent_logs.PolicyAlarmController{},
),
),
beego.NSNamespace("/error",
beego.NSInclude(
&agent_logs.ErrorController{},
),
),
),
beego.NSNamespace("/rasp",
beego.NSInclude(
&agent.RaspController{},
),
),
beego.NSNamespace("/report",
beego.NSInclude(
&agent.ReportController{},
),
),
)
foregroudNS := beego.NewNamespace("/api",
beego.NSNamespace("/plugin",
beego.NSInclude(
&api.PluginController{},
),
),
beego.NSNamespace("/log",
beego.NSNamespace("/attack",
beego.NSInclude(
&fore_logs.AttackAlarmController{},
),
),
beego.NSNamespace("/policy",
beego.NSInclude(
&fore_logs.PolicyAlarmController{},
),
),
beego.NSNamespace("/error",
beego.NSInclude(
&fore_logs.ErrorController{},
),
),
),
beego.NSNamespace("/app",
beego.NSInclude(
&api.AppController{},
),
),
beego.NSNamespace("/rasp",
beego.NSInclude(
&api.RaspController{},
),
),
beego.NSNamespace("/token",
beego.NSInclude(
&api.TokenController{},
),
),
beego.NSNamespace("/report",
beego.NSInclude(
&api.ReportController{},
),
),
beego.NSNamespace("/operation",
beego.NSInclude(
&api.OperationController{},
),
),
beego.NSNamespace("/server",
beego.NSInclude(
&api.ServerController{},
),
),
)
userNS := beego.NewNamespace("/user", beego.NSInclude(&api.UserController{}))
pingNS := beego.NewNamespace("/ping", beego.NSInclude(&controllers.PingController{}))
ns := beego.NewNamespace("/v1")
ns.Namespace(pingNS)
startType := *conf.AppConfig.Flag.StartType
if startType == conf.StartTypeForeground {
ns.Namespace(foregroudNS, userNS)
} else if startType == conf.StartTypeAgent {
ns.Namespace(agentNS)
} else if startType == conf.StartTypeDefault {
ns.Namespace(foregroudNS, agentNS, userNS)
} else {
tools.Panic(tools.ErrCodeStartTypeNotSupport, "The start type is not supported: "+startType, nil)
}
if startType == conf.StartTypeForeground || startType == conf.StartTypeDefault {
beego.SetStaticPath("//", tools.GetCurrentPathWithPanic()+"/dist")
}
beego.AddNamespace(ns)
}
package inits
import (
"fmt"
"path/filepath"
"rasp-cloud/tools"
"github.com/bouk/monkey"
"bytes"
"net/http/httptest"
"github.com/astaxie/beego"
"net/http"
"encoding/json"
. "github.com/smartystreets/goconvey/convey"
)
type Response struct {
Status int `json:"status"`
Desc string `json:"description"`
Data interface{} `json:"data"`
}
func init() {
tools.GetCurrentPath()
tools.PathExists("/xxx/xxx/xxxxxxx")
apppath, _ := filepath.Abs(filepath.Dir("./"))
monkey.Patch(tools.GetCurrentPath, func() (string, error) {
return apppath, nil
})
fmt.Println(tools.GetCurrentPath())
}
func GetResponse(method string, path string, body string) (*Response) {
r, _ := http.NewRequest(method, path, bytes.NewBuffer([]byte(body)))
w := httptest.NewRecorder()
beego.BeeApp.Handlers.ServeHTTP(w, r)
response := &Response{}
So(w.Code, ShouldEqual, 200)
err := json.Unmarshal(w.Body.Bytes(), response)
So(err, ShouldEqual, nil)
return response
}
func GetResponseRecorder(method string, path string, body string) (*httptest.ResponseRecorder) {
r, _ := http.NewRequest(method, path, bytes.NewBuffer([]byte(body)))
w := httptest.NewRecorder()
beego.BeeApp.Handlers.ServeHTTP(w, r)
return w
}
func GetJson(data interface{}) string {
jsonBytes, _ := json.Marshal(data)
return string(jsonBytes)
}
func GetLongString(length int) string {
result := ""
for i := 0; i < length; i++ {
result += "a"
}
return result
}
func GetLongStringArray(length int) []string {
result := make([]string, length)
for i := 0; i < length; i++ {
result[i] = "a"
}
return result
}
package start
import (
_ "rasp-cloud/tests/inits"
_ "rasp-cloud/environment"
_ "rasp-cloud/models"
//_ "rasp-cloud/filter"
_ "rasp-cloud/controllers"
"rasp-cloud/routers"
"github.com/astaxie/beego"
"rasp-cloud/controllers"
"github.com/astaxie/beego/context"
"rasp-cloud/models"
)
var TestApp = &models.App{
Name: "test_app",
Language: "java",
Description: "test app",
}
var online = false
var TestRasp = &models.Rasp{
Id: "1234567890abc121321354545135135",
Language: "java",
Version: "1.0",
HostName: "ubuntu",
RegisterIp: "10.23.25.36",
LanguageVersion: "1.8",
ServerType: "tomcat",
RaspHome: "/home/work/tomcat8",
PluginVersion: "2019-03-10-10000",
HeartbeatInterval: 180,
LastHeartbeatTime: 1551781949000,
RegisterTime: 1551781949000,
Environ: map[string]string{},
Online: &online,
}
func init() {
routers.InitRouter()
beego.ErrorController(&controllers.ErrorController{})
beego.BConfig.RecoverFunc = func(*context.Context) {
if err := recover(); err != nil {
}
}
count, apps, _ := models.GetAllApp(1, 1, false)
if count > 0 {
TestApp = apps[0]
} else {
TestApp, _ = models.AddApp(TestApp)
}
TestRasp.AppId = TestApp.Id
models.UpsertRaspById(TestRasp.Id, TestRasp)
}
package tools
import "net/smtp"
type loginAuth struct {
username, password string
}
func LoginAuth(username, password string) smtp.Auth {
return &loginAuth{username, password}
}
func (auth *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
return "LOGIN", []byte(auth.username), nil
}
func (auth *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
if more {
switch string(fromServer) {
case "Username:":
return []byte(auth.username), nil
case "Password:":
return []byte(auth.password), nil
}
}
return nil, nil
}
//Copyright 2017-2019 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package tools
import (
"github.com/astaxie/beego/logs"
"strconv"
"os"
)
const (
ErrCodeLogInitFailed = 30001 + iota
ErrCodeMongoInitFailed
ErrCodeESInitFailed
ErrCodeConfigInitFailed
ErrCodeStartTypeNotSupport
ErrCodeGeneratePasswdFailed
ErrCodeGeoipInit
ErrCodeResetUserFailed
ErrCodeInitDefaultAppFailed
ErrCodeInitChildProcessFailed
)
func Panic(errCode int, message string, err error) {
message = "[" + strconv.Itoa(errCode) + "] " + message
if err != nil {
message = message + ": " + err.Error()
}
logs.Error(message)
os.Exit(errCode)
}
//Copyright 2017-2019 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package tools
import (
"os"
"strings"
"path/filepath"
"os/exec"
"errors"
)
func GetCurrentPathWithPanic() string {
currentPath, err := GetCurrentPath()
if err != nil {
Panic(ErrCodeLogInitFailed, "failed to get current path", err)
}
return currentPath
}
func GetCurrentPath() (string, error) {
file, err := exec.LookPath(os.Args[0])
if err != nil {
return "", err
}
path, err := filepath.Abs(file)
if err != nil {
return "", err
}
i := strings.LastIndex(path, "/")
if i < 0 {
i = strings.LastIndex(path, "\\")
}
if i < 0 {
return "", errors.New(`error: Can't find "/" or "\"`)
}
return string(path[0 : i+1]), nil
}
func PathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
//Copyright 2017-2019 Baidu Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http: //www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
package tools
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
"github.com/astaxie/beego/logs"
)
var (
AdapterAlarmFile = "alarm_file_logger "
)
// RaspFileLogWriter implements LoggerInterface.
// It writes messages by lines limit, file size limit, or time frequency.
type RaspFileLogWriter struct {
sync.RWMutex // write log order by order and atomic incr maxLinesCurLines and maxSizeCurSize
// The opened file
Filename string `json:"filename"`
fileWriter *os.File
// Rotate at line
MaxLines int `json:"maxlines"`
maxLinesCurLines int
MaxFiles int `json:"maxfiles"`
MaxFilesCurFiles int
// Rotate at size
MaxSize int `json:"maxsize"`
maxSizeCurSize int
// Rotate daily
Daily bool `json:"daily"`
MaxDays int64 `json:"maxdays"`
dailyOpenDate int
dailyOpenTime time.Time
Rotate bool `json:"rotate"`
Level int `json:"level"`
Perm string `json:"perm"`
RotatePerm string `json:"rotateperm"`
fileNameOnly, suffix string // like "project.log", project is fileNameOnly and .log is suffix
}
// newFileWriter create a FileLogWriter returning as LoggerInterface.
func NewFileWriter() logs.Logger {
w := &RaspFileLogWriter{
Daily: true,
MaxDays: 7,
Rotate: true,
RotatePerm: "0440",
Level: logs.LevelTrace,
Perm: "0660",
MaxLines: 10000000,
MaxFiles: 999,
MaxSize: 1 << 28,
}
return w
}
// Init file logger with json config.
// jsonConfig like:
// {
// "filename":"logs/beego.log",
// "maxLines":10000,
// "maxsize":1024,
// "daily":true,
// "maxDays":15,
// "rotate":true,
// "perm":"0600"
// }
func (w *RaspFileLogWriter) Init(jsonConfig string) error {
err := json.Unmarshal([]byte(jsonConfig), w)
if err != nil {
return err
}
if len(w.Filename) == 0 {
return errors.New("jsonconfig must have filename")
}
w.suffix = filepath.Ext(w.Filename)
w.fileNameOnly = strings.TrimSuffix(w.Filename, w.suffix)
if w.suffix == "" {
w.suffix = ".log"
}
err = w.startLogger()
return err
}
// start file logger. create log file and set to locker-inside file writer.
func (w *RaspFileLogWriter) startLogger() error {
file, err := w.createLogFile()
if err != nil {
return err
}
if w.fileWriter != nil {
w.fileWriter.Close()
}
w.fileWriter = file
return w.initFd()
}
func (w *RaspFileLogWriter) NeedRotate(size int, day int) bool {
return (w.MaxLines > 0 && w.maxLinesCurLines >= w.MaxLines) ||
(w.MaxSize > 0 && w.maxSizeCurSize >= w.MaxSize) ||
(w.Daily && day != w.dailyOpenDate)
}
// WriteMsg write logger message into file.
func (w *RaspFileLogWriter) WriteMsg(when time.Time, msg string, level int) error {
if level > w.Level {
return nil
}
_, _, d := when.Date()
msg = msg + "\n"
if w.Rotate {
w.RLock()
if w.NeedRotate(len(msg), d) {
w.RUnlock()
w.Lock()
if w.NeedRotate(len(msg), d) {
if err := w.DoRotate(when); err != nil {
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
}
}
w.Unlock()
} else {
w.RUnlock()
}
}
w.Lock()
_, err := w.fileWriter.Write([]byte(msg))
if err == nil {
w.maxLinesCurLines++
w.maxSizeCurSize += len(msg)
}
w.Unlock()
return err
}
func (w *RaspFileLogWriter) createLogFile() (*os.File, error) {
// Open the log file
perm, err := strconv.ParseInt(w.Perm, 8, 64)
if err != nil {
return nil, err
}
filepath := path.Dir(w.Filename)
os.MkdirAll(filepath, os.FileMode(perm))
fd, err := os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(perm))
if err == nil {
// Make sure file perm is user set perm cause of `os.OpenFile` will obey umask
os.Chmod(w.Filename, os.FileMode(perm))
}
return fd, err
}
func (w *RaspFileLogWriter) initFd() error {
fd := w.fileWriter
fInfo, err := fd.Stat()
if err != nil {
return fmt.Errorf("get stat err: %s", err)
}
w.maxSizeCurSize = int(fInfo.Size())
w.dailyOpenTime = time.Now()
w.dailyOpenDate = w.dailyOpenTime.Day()
w.maxLinesCurLines = 0
if w.Daily {
go w.dailyRotate(w.dailyOpenTime)
}
if fInfo.Size() > 0 && w.MaxLines > 0 {
count, err := w.lines()
if err != nil {
return err
}
w.maxLinesCurLines = count
}
return nil
}
func (w *RaspFileLogWriter) dailyRotate(openTime time.Time) {
y, m, d := openTime.Add(24 * time.Hour).Date()
nextDay := time.Date(y, m, d, 0, 0, 0, 0, openTime.Location())
tm := time.NewTimer(time.Duration(nextDay.UnixNano() - openTime.UnixNano() + 100))
<-tm.C
w.Lock()
if w.NeedRotate(0, time.Now().Day()) {
if err := w.DoRotate(time.Now()); err != nil {
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
}
}
w.Unlock()
}
func (w *RaspFileLogWriter) lines() (int, error) {
fd, err := os.Open(w.Filename)
if err != nil {
return 0, err
}
defer fd.Close()
buf := make([]byte, 32768) // 32k
count := 0
lineSep := []byte{'\n'}
for {
c, err := fd.Read(buf)
if err != nil && err != io.EOF {
return count, err
}
count += bytes.Count(buf[:c], lineSep)
if err == io.EOF {
break
}
}
return count, nil
}
// DoRotate means it need to write file in new file.
// new file name like xx.2013-01-01.log (daily) or xx.001.log (by line or size)
func (w *RaspFileLogWriter) DoRotate(logTime time.Time) error {
// file exists
// Find the next available number
num := w.MaxFilesCurFiles + 1
fName := ""
rotatePerm, err := strconv.ParseInt(w.RotatePerm, 8, 64)
if err != nil {
return err
}
_, err = os.Lstat(w.Filename)
if err != nil {
//even if the file is not exist or other ,we should RESTART the logger
goto RESTART_LOGGER
}
// only when one of them be setted, then the file would be splited
if w.MaxLines > 0 || w.MaxSize > 0 {
for ; err == nil && num <= w.MaxFiles; num++ {
fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format("2006-01-02"), num, w.suffix)
_, err = os.Lstat(fName)
}
} else {
fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", w.dailyOpenTime.Format("2006-01-02"), num, w.suffix)
_, err = os.Lstat(fName)
w.MaxFilesCurFiles = num
}
// return error if the last file checked still existed
if err == nil {
return fmt.Errorf("Rotate: Cannot find free log number to rename %s", w.Filename)
}
// close fileWriter before rename
w.fileWriter.Close()
// Rename the file to its new found name
// even if occurs error,we MUST guarantee to restart new logger
err = os.Rename(w.Filename, fName)
if err != nil {
goto RESTART_LOGGER
}
err = os.Chmod(fName, os.FileMode(rotatePerm))
RESTART_LOGGER:
startLoggerErr := w.startLogger()
go w.deleteOldLog()
if startLoggerErr != nil {
return fmt.Errorf("Rotate StartLogger: %s", startLoggerErr)
}
if err != nil {
return fmt.Errorf("Rotate: %s", err)
}
return nil
}
func (w *RaspFileLogWriter) deleteOldLog() {
dir := filepath.Dir(w.Filename)
filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) {
defer func() {
if r := recover(); r != nil {
fmt.Fprintf(os.Stderr, "Unable to delete old log '%s', error: %v\n", path, r)
}
}()
if info == nil {
return
}
if !info.IsDir() && info.ModTime().Add(24 * time.Hour * time.Duration(w.MaxDays)).Before(time.Now()) {
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) &&
strings.HasSuffix(filepath.Base(path), w.suffix) {
os.Remove(path)
}
}
return
})
}
// Destroy close the file description, close file writer.
func (w *RaspFileLogWriter) Destroy() {
w.fileWriter.Close()
}
// Flush flush file logger.
// there are no buffering messages in file logger in memory.
// flush file means sync file from disk.
func (w *RaspFileLogWriter) Flush() {
w.fileWriter.Sync()
}
func init() {
logs.Register(AdapterAlarmFile, NewFileWriter)
}