Signed-off-by: xiang <xiang13225080@163.com>
This commit is contained in:
xiang 2022-01-07 10:56:14 +08:00
parent 7103539ffe
commit d50e985cbc
4 changed files with 714 additions and 0 deletions

155
cmd/attack/file.go Normal file
View File

@ -0,0 +1,155 @@
// Copyright 2020 Chaos Mesh Authors.
//
// 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,
// See the License for the specific language governing permissions and
// limitations under the License.
package attack
import (
"fmt"
"github.com/spf13/cobra"
"go.uber.org/fx"
"github.com/chaos-mesh/chaosd/cmd/server"
"github.com/chaos-mesh/chaosd/pkg/core"
"github.com/chaos-mesh/chaosd/pkg/server/chaosd"
"github.com/chaos-mesh/chaosd/pkg/utils"
)
func NewFileAttackCommand() *cobra.Command {
options := core.NewFileCommand()
dep := fx.Options(
server.Module,
fx.Provide(func() *core.FileCommand {
return options
}),
)
cmd := &cobra.Command{
Use: "file <subcommand>",
Short: "File attack related commands",
}
cmd.AddCommand(
NewFileCreateCommand(dep, options),
NewFileModifyPrivilegeCommand(dep, options),
NewFileDeleteCommand(dep, options),
NewFileRenameCommand(dep, options),
NewFileAppendCommand(dep, options),
)
return cmd
}
func NewFileCreateCommand(dep fx.Option, options *core.FileCommand) *cobra.Command {
cmd := &cobra.Command{
Use: "create",
Short: "create file",
Run: func(*cobra.Command, []string) {
options.Action = core.FileCreateAction
options.CompleteDefaults()
utils.FxNewAppWithoutLog(dep, fx.Invoke(commonFileAttackFunc)).Run()
},
}
cmd.Flags().StringVarP(&options.FileName, "filename", "f", "", "create file based on filename")
cmd.Flags().StringVarP(&options.DirName, "dirname", "d", "", "create directory based on dirname")
cmd.Flags().StringVarP(&options.DestDir, "destdir", "", "", "create a file or directory to the specified destdir")
// owner TODO
return cmd
}
func NewFileModifyPrivilegeCommand(dep fx.Option, options *core.FileCommand) *cobra.Command {
cmd := &cobra.Command{
Use: "modify",
Short: "modify file privilege",
Run: func(cmd *cobra.Command, args []string) {
options.Action = core.FileModifyPrivilegeAction
utils.FxNewAppWithoutLog(dep, fx.Invoke(commonFileAttackFunc)).Run()
},
}
cmd.Flags().StringVarP(&options.FileName, "filename", "f", "", "file to be change privilege")
cmd.Flags().Uint32VarP(&options.Privilege, "privilege", "p", 0, "privilege to be update")
return cmd
}
func NewFileDeleteCommand(dep fx.Option, options *core.FileCommand) *cobra.Command {
cmd := &cobra.Command{
Use: "delete",
Short: "delete file",
Run: func(cmd *cobra.Command, args []string) {
options.Action = core.FileDeleteAction
options.CompleteDefaults()
utils.FxNewAppWithoutLog(dep, fx.Invoke(commonFileAttackFunc)).Run()
},
}
cmd.Flags().StringVarP(&options.FileName, "filename", "f", "", "delete file based on filename")
cmd.Flags().StringVarP(&options.DirName, "dirname", "d", "", "delete directory based on dirname")
cmd.Flags().StringVarP(&options.DestDir, "destdir", "", "", "delete a file or directory to the specified destdir")
return cmd
}
func NewFileRenameCommand(dep fx.Option, options *core.FileCommand) *cobra.Command {
cmd := &cobra.Command{
Use: "rename",
Short: "rename file",
Run: func(cmd *cobra.Command, args []string) {
options.Action = core.FileRenameAction
utils.FxNewAppWithoutLog(dep, fx.Invoke(commonFileAttackFunc)).Run()
},
}
cmd.Flags().StringVarP(&options.SourceFile, "source-file", "s", "", "the source file/dir of rename")
cmd.Flags().StringVarP(&options.DstFile, "dst-file", "d", "", "the destination file/dir of rename")
return cmd
}
func NewFileAppendCommand(dep fx.Option, options *core.FileCommand) *cobra.Command {
cmd := &cobra.Command{
Use: "append",
Short: "append file",
Run: func(cmd *cobra.Command, args []string) {
options.Action = core.FileAppendAction
utils.FxNewAppWithoutLog(dep, fx.Invoke(commonFileAttackFunc)).Run()
},
}
cmd.Flags().StringVarP(&options.FileName, "filename", "f", "", "append data to the file")
cmd.Flags().StringVarP(&options.Data, "data", "d", "", "append data")
cmd.Flags().IntVarP(&options.Count, "count", "c", 1, "append count with default value is 1")
cmd.Flags().IntVarP(&options.LineNo, "line", "l", 0, "the start line to append with default value is 1")
return cmd
}
func commonFileAttackFunc(options *core.FileCommand, chaos *chaosd.Server) {
if err := options.Validate(); err != nil {
utils.ExitWithError(utils.ExitBadArgs, err)
}
uid, err := chaos.ExecuteAttack(chaosd.FileAttack, options, core.CommandMode)
if err != nil {
utils.ExitWithError(utils.ExitError, err)
}
utils.NormalExit(fmt.Sprintf("Attack file successfully, uid: %s", uid))
}

View File

@ -38,6 +38,7 @@ const (
ClockAttack = "clock"
HostAttack = "host"
JVMAttack = "jvm"
FileAttack = "file"
)
const (

185
pkg/core/file.go Normal file
View File

@ -0,0 +1,185 @@
// Copyright 2020 Chaos Mesh Authors.
//
// 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,
// See the License for the specific language governing permissions and
// limitations under the License.
package core
import (
"bufio"
"encoding/json"
"github.com/pingcap/errors"
"github.com/pingcap/log"
"go.uber.org/zap"
"os"
)
type FileCommand struct {
CommonAttackConfig
FileName string
DirName string
DestDir string
Privilege uint32
SourceFile string
DstFile string
Data string
Count int
LineNo int
FileMode int
}
var _ AttackConfig = &FileCommand{}
const (
FileCreateAction = "create"
FileModifyPrivilegeAction = "modify"
FileDeleteAction = "delete"
FileRenameAction = "rename"
FileAppendAction = "append"
)
func (n *FileCommand) Validate() error {
if err := n.CommonAttackConfig.Validate(); err != nil {
return err
}
switch n.Action {
case FileCreateAction:
return n.validFileCreate()
case FileModifyPrivilegeAction:
return n.validFileModify()
case FileDeleteAction:
return n.validFileDelete()
case FileRenameAction:
return n.validFileRename()
case FileAppendAction:
return n.valieFileAppend()
default:
return errors.Errorf("file action %s not supported", n.Action)
}
}
func (n *FileCommand) validFileCreate() error {
if len(n.FileName) == 0 && len(n.DirName) == 0 {
return errors.New("filename and dirname can not all null")
}
return nil
}
func (n *FileCommand) validFileModify() error {
if len(n.FileName) == 0 {
return errors.New("filename can not null")
}
if n.Privilege == 0 {
return errors.New("file privilege can not null")
}
return nil
}
func (n *FileCommand) validFileDelete() error {
if len(n.FileName) == 0 && len(n.DirName) == 0 {
return errors.New("filename and dirname can not all null")
}
return nil
}
func (n *FileCommand) validFileRename() error {
if len(n.SourceFile) == 0 || len(n.DstFile) == 0 {
return errors.New("source file and destination file must have value")
}
return nil
}
func (n *FileCommand) valieFileAppend() error {
if len(n.FileName) == 0 {
return errors.New("filename can not null")
}
if len(n.Data) == 0 {
return errors.New("append data can not null")
}
return nil
}
func (n *FileCommand) CompleteDefaults() {
switch n.Action {
case FileCreateAction:
n.setDefaultForFileCreate()
case FileDeleteAction:
n.setDefaultForFileDelete()
case FileAppendAction:
n.setDefaultForFileAppend()
}
}
func (n *FileCommand) setDefaultForFileCreate() {
if len(n.FileName) == 0 && len(n.DirName) == 0 {
n.FileName = "chaosd.file"
}
if len(n.DestDir) > 0 {
n.DestDir = n.DestDir + "/"
}
}
func (n *FileCommand) setDefaultForFileDelete() {
if len(n.DestDir) > 0 {
n.DestDir = n.DestDir + "/"
}
}
func (n *FileCommand) setDefaultForFileAppend() {
if n.Count == 0 {
n.Count = 1
}
fileNumber := GetFileNumber(n.FileName)
if n.LineNo == 0 {
n.LineNo = fileNumber + 1
}
}
func GetFileNumber(fileName string) int {
file, err := os.Open(fileName)
if err != nil{
log.Error("open file error", zap.Error(err))
}
defer file.Close()
fd := bufio.NewReader(file)
count := 0
for {
_, err := fd.ReadString('\n')
if err!= nil{
break
}
count++
}
return count
}
func (n FileCommand) RecoverData() string {
data, _ := json.Marshal(n)
return string(data)
}
func NewFileCommand() *FileCommand {
return &FileCommand{
CommonAttackConfig: CommonAttackConfig{
Kind: FileAttack,
},
}
}

373
pkg/server/chaosd/file.go Normal file
View File

@ -0,0 +1,373 @@
// Copyright 2020 Chaos Mesh Authors.
//
// 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,
// See the License for the specific language governing permissions and
// limitations under the License.
package chaosd
import (
"fmt"
"github.com/chaos-mesh/chaosd/pkg/core"
"github.com/pingcap/errors"
"github.com/pingcap/log"
"go.uber.org/zap"
"os"
"os/exec"
"strconv"
"strings"
)
type fileAttack struct{}
var FileAttack AttackType = fileAttack{}
func (fileAttack) Attack(options core.AttackConfig, env Environment) (err error) {
attack := options.(*core.FileCommand)
switch attack.Action {
case core.FileCreateAction:
if err = env.Chaos.createFile(attack, env.AttackUid); err != nil {
return errors.WithStack(err)
}
case core.FileModifyPrivilegeAction:
if err = env.Chaos.modifyFilePrivilege(attack, env.AttackUid); err != nil {
return errors.WithStack(err)
}
case core.FileDeleteAction:
if err = env.Chaos.deleteFile(attack, env.AttackUid); err != nil {
return errors.WithStack(err)
}
case core.FileRenameAction:
if err = env.Chaos.renameFile(attack, env.AttackUid); err != nil {
return errors.WithStack(err)
}
case core.FileAppendAction:
if err = env.Chaos.appendFile(attack, env.AttackUid); err != nil {
return errors.WithStack(err)
}
}
return nil
}
func (s *Server) createFile(attack *core.FileCommand, uid string) error {
var err error
if len(attack.FileName) > 0 {
_, err = os.Create(attack.DestDir + attack.FileName)
} else if len(attack.DirName) > 0 {
err = os.Mkdir(attack.DestDir+attack.DirName, os.ModePerm)
}
if err != nil {
log.Error("create file/dir failed", zap.Error(err))
return errors.WithStack(err)
}
return nil
}
func (s *Server) modifyFilePrivilege(attack *core.FileCommand, uid string) error {
cmdStr := "stat -c %a" + " "+ attack.FileName
cmd := exec.Command("bash", "-c", cmdStr)
output, err := cmd.CombinedOutput()
if err != nil {
log.Error(string(output), zap.Error(err))
return errors.WithStack(err)
}
str1 := strings.Replace(string(output), "\n", "", -1)
attack.FileMode, err = strconv.Atoi(string(str1))
if err != nil {
log.Error(str1, zap.Error(err))
return errors.WithStack(err)
}
cmdStr = fmt.Sprintf("chmod %d %s", attack.Privilege, attack.FileName)
cmd = exec.Command("bash", "-c", cmdStr)
output, err = cmd.CombinedOutput()
if err != nil {
log.Error(string(output), zap.Error(err))
return errors.WithStack(err)
}
log.Info(string(output))
return nil
}
func (s *Server) deleteFile(attack *core.FileCommand, uid string) error {
var err error
if len(attack.FileName) > 0 {
backFile := attack.DestDir + attack.FileName + "." + uid
err = os.Rename(attack.DestDir+attack.FileName, backFile)
} else if len(attack.DirName) > 0 {
backDir := attack.DestDir + attack.DirName + "." + uid
err = os.Rename(attack.DestDir+attack.DirName, backDir)
}
if err != nil {
log.Error("create file/dir faild", zap.Error(err))
return errors.WithStack(err)
}
return nil
}
func (s *Server) renameFile(attack *core.FileCommand, uid string) error {
err := os.Rename(attack.SourceFile, attack.DstFile)
if err != nil {
log.Error("create file/dir faild", zap.Error(err))
return errors.WithStack(err)
}
return nil
}
//while the input content has many lines, "\n" is the line break
func (s *Server) appendFile(attack *core.FileCommand, uid string) error {
if fileEmpty(attack.FileName) {
for i := 0; i < attack.Count; i++ {
cmdStr := fmt.Sprintf("echo -e '%s' >> %s", attack.Data, attack.FileName)
cmd := exec.Command("bash", "-c", cmdStr)
output, err := cmd.CombinedOutput()
if err != nil {
println("append data exec echo error")
log.Error(cmd.String()+string(output), zap.Error(err))
return errors.WithStack(err)
}
log.Info(string(output))
}
} else {
if attack.LineNo == 0 {
//在文件头部,第一行插入
cmdStr := fmt.Sprintf("sed -i '1i %s' %s", attack.Data, attack.FileName)
for i := 0; i < attack.Count; i++ {
cmd := exec.Command("bash", "-c", cmdStr)
output, err := cmd.CombinedOutput()
if err != nil {
log.Error(cmd.String()+string(output), zap.Error(err))
return errors.WithStack(err)
}
log.Info(string(output))
}
} else {
//这种方式只能在第一行之后插入
//实验开始前检测test.dat文件是否存在如果存在删除
if fileExist("test.dat") {
if err := deleteTestFile("test.dat"); err != nil {
return errors.WithStack(err)
}
}
println("fileExist has run success")
//1. 插入的字符串转为文件
file, err := generateFile(attack.Data)
if err != nil {
println("generate file error")
log.Error("generate file from input data err", zap.Error(err))
return errors.WithStack(err)
}
println("generate file success")
//2. 生成的文件插入指定的行 利用sed -i
c := fmt.Sprintf("%d r %s", attack.LineNo, file.Name())
cmdStr := fmt.Sprintf("sed -i '%s' %s", c, attack.FileName)
fmt.Println("cmd str is %s", cmdStr)
for i := 0; i < attack.Count; i++ {
cmd := exec.Command("bash", "-c", cmdStr)
output, err := cmd.CombinedOutput()
if err != nil {
println("append data exec cat error")
log.Error(cmd.String()+string(output), zap.Error(err))
return errors.WithStack(err)
}
log.Info(string(output))
}
}
}
return nil
}
func deleteTestFile(file string) error {
cmdStr := fmt.Sprintf("rm -rf %s", file)
cmd := exec.Command("bash", "-c", cmdStr)
output, err := cmd.CombinedOutput()
if err != nil {
fmt.Print("delete test file error")
log.Error(string(output), zap.Error(err))
return errors.WithStack(err)
}
log.Info(string(output))
return nil
}
func fileExist(fileName string) bool {
_, err := os.Lstat(fileName)
return !os.IsNotExist(err)
}
func fileEmpty(fileName string) bool {
file, err := os.Stat(fileName)
if err != nil {
log.Error("get file is empty err", zap.Error(err))
}
if file.Size() ==0 {
return true
}
return false
}
func generateFile(s string) (*os.File, error) {
fileName := "test.dat"
dstFile,err := os.Create(fileName)
if err!=nil{
fmt.Println(err.Error())
return dstFile, err
}
defer dstFile.Close()
strNew := strings.Replace(s, `\n`, "\n", -1)
dstFile.WriteString(strNew)
return dstFile, nil
}
func (fileAttack) Recover(exp core.Experiment, env Environment) error {
config, err := exp.GetRequestCommand()
if err != nil {
return err
}
attack := config.(*core.FileCommand)
switch attack.Action {
case core.FileCreateAction:
if err = env.Chaos.recoverCreateFile(attack); err != nil {
return errors.WithStack(err)
}
case core.FileModifyPrivilegeAction:
if err = env.Chaos.recoverModifyPrivilege(attack); err != nil {
return errors.WithStack(err)
}
case core.FileDeleteAction:
if err = env.Chaos.recoverDeleteFile(attack, env.AttackUid); err != nil {
return errors.WithStack(err)
}
case core.FileRenameAction:
if err = env.Chaos.recoverRenameFile(attack); err != nil {
return errors.WithStack(err)
}
case core.FileAppendAction:
if err = env.Chaos.recoverAppendFile(attack); err != nil {
return errors.WithStack(err)
}
}
return nil
}
func (s *Server) recoverCreateFile(attack *core.FileCommand) error {
var err error
if len(attack.FileName) > 0 {
err = os.Remove(attack.DestDir + attack.FileName)
} else if len(attack.DirName) > 0 {
err = os.RemoveAll(attack.DestDir + attack.DirName)
}
if err != nil {
log.Error("delete file/dir faild", zap.Error(err))
return errors.WithStack(err)
}
return nil
}
func (s *Server) recoverModifyPrivilege(attack *core.FileCommand) error {
cmdStr := fmt.Sprintf("chmod %d %s", attack.FileMode, attack.FileName)
cmd := exec.Command("bash", "-c", cmdStr)
output, err := cmd.CombinedOutput()
if err != nil {
log.Error(string(output), zap.Error(err))
return errors.WithStack(err)
}
return nil
}
func (s *Server) recoverDeleteFile(attack *core.FileCommand, uid string) error {
var err error
if len(attack.FileName) > 0 {
backFile := attack.DestDir + attack.FileName + "." + uid
err = os.Rename(backFile, attack.DestDir+attack.FileName)
} else if len(attack.DirName) > 0 {
backDir := attack.DestDir + attack.DirName + "." + uid
err = os.Rename(backDir, attack.DestDir+attack.DirName)
}
if err != nil {
log.Error("recover delete file/dir faild", zap.Error(err))
return errors.WithStack(err)
}
return nil
}
func (s *Server) recoverRenameFile(attack *core.FileCommand) error {
err := os.Rename(attack.DstFile, attack.SourceFile)
if err != nil {
log.Error("recover rename file/dir faild", zap.Error(err))
return errors.WithStack(err)
}
return nil
}
func (s *Server) recoverAppendFile(attack *core.FileCommand) error {
//实验结束的时候将生成的临时文件test.dat删除
if fileExist("test.dat") {
if err := deleteTestFile("test.dat"); err != nil {
return errors.WithStack(err)
}
}
//计算插入的行数
linesByInput := attack.Count * core.GetFileNumber(attack.FileName)
//从插入的行开始删除,这么多行
c := fmt.Sprintf("%d,%dd", attack.LineNo+1, attack.LineNo+linesByInput)
cmdStr := fmt.Sprintf("sed -i '%s' %s", c, attack.FileName)
cmd := exec.Command("bash", "-c", cmdStr)
output, err := cmd.CombinedOutput()
if err != nil {
log.Error(string(output), zap.Error(err))
return errors.WithStack(err)
}
return nil
}