executor.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. package executor
  2. import (
  3. pb "github.com/jamesread/OliveTin/gen/grpc"
  4. acl "github.com/jamesread/OliveTin/internal/acl"
  5. config "github.com/jamesread/OliveTin/internal/config"
  6. log "github.com/sirupsen/logrus"
  7. "context"
  8. "errors"
  9. "os/exec"
  10. "regexp"
  11. "strings"
  12. "time"
  13. )
  14. var (
  15. typecheckRegex = map[string]string{
  16. "very_dangerous_raw_string": "",
  17. "int": "^[\\d]+$",
  18. "ascii": "^[a-zA-Z0-9]+$",
  19. "ascii_identifier": "^[a-zA-Z0-9\\-\\.\\_]+$",
  20. "ascii_sentence": "^[a-zA-Z0-9 \\,\\.]+$",
  21. }
  22. )
  23. type InternalLogEntry struct {
  24. Datetime string
  25. Stdout string
  26. Stderr string
  27. TimedOut bool
  28. ExitCode int32
  29. /*
  30. The following two properties are obviously on Action normally, but it's useful
  31. that logs are lightweight (so we don't need to have an action associated to
  32. logs, etc. Therefore, we duplicate those values here.
  33. */
  34. ActionTitle string
  35. ActionIcon string
  36. }
  37. type ExecutionRequest struct {
  38. ActionName string
  39. Arguments map[string]string
  40. action *config.Action
  41. Cfg *config.Config
  42. User *acl.User
  43. logEntry *InternalLogEntry
  44. finalParsedCommand string
  45. }
  46. type ExecutorStep interface {
  47. Exec(*ExecutionRequest) bool
  48. }
  49. type Executor struct {
  50. Logs []InternalLogEntry
  51. chainOfCommand []ExecutorStep
  52. }
  53. func DefaultExecutor() *Executor {
  54. e := Executor{}
  55. e.chainOfCommand = []ExecutorStep{
  56. StepFindAction{},
  57. StepAclCheck{},
  58. StepParseArgs{},
  59. StepLogStart{},
  60. StepExec{},
  61. StepLogFinish{},
  62. }
  63. return &e
  64. }
  65. type StepFindAction struct{}
  66. func (s StepFindAction) Exec(req *ExecutionRequest) bool {
  67. actualAction := req.Cfg.FindAction(req.ActionName)
  68. if actualAction == nil {
  69. log.WithFields(log.Fields{
  70. "actionName": req.ActionName,
  71. }).Warnf("Action not found")
  72. req.logEntry.Stderr = "Action not found"
  73. req.logEntry.ExitCode = -1337
  74. return false
  75. }
  76. req.action = actualAction
  77. req.logEntry.ActionIcon = actualAction.Icon
  78. return true
  79. }
  80. type StepAclCheck struct{}
  81. func (s StepAclCheck) Exec(req *ExecutionRequest) bool {
  82. return acl.IsAllowedExec(req.Cfg, req.User, req.action)
  83. }
  84. // ExecRequest processes an ExecutionRequest
  85. func (e *Executor) ExecRequest(req *ExecutionRequest) *pb.StartActionResponse {
  86. req.logEntry = &InternalLogEntry{
  87. Datetime: time.Now().Format("2006-01-02 15:04:05"),
  88. ActionTitle: req.ActionName,
  89. }
  90. for _, step := range e.chainOfCommand {
  91. if !step.Exec(req) {
  92. break
  93. }
  94. }
  95. e.Logs = append(e.Logs, *req.logEntry)
  96. return &pb.StartActionResponse{
  97. LogEntry: &pb.LogEntry{
  98. ActionTitle: req.logEntry.ActionTitle,
  99. ActionIcon: req.logEntry.ActionIcon,
  100. Datetime: req.logEntry.Datetime,
  101. Stderr: req.logEntry.Stderr,
  102. Stdout: req.logEntry.Stdout,
  103. TimedOut: req.logEntry.TimedOut,
  104. ExitCode: req.logEntry.ExitCode,
  105. },
  106. }
  107. }
  108. type StepLogStart struct{}
  109. func (e StepLogStart) Exec(req *ExecutionRequest) bool {
  110. log.WithFields(log.Fields{
  111. "title": req.action.Title,
  112. "timeout": req.action.Timeout,
  113. }).Infof("Action starting")
  114. return true
  115. }
  116. type StepLogFinish struct{}
  117. func (e StepLogFinish) Exec(req *ExecutionRequest) bool {
  118. log.WithFields(log.Fields{
  119. "title": req.action.Title,
  120. "stdout": req.logEntry.Stdout,
  121. "stderr": req.logEntry.Stderr,
  122. "timedOut": req.logEntry.TimedOut,
  123. "exit": req.logEntry.ExitCode,
  124. }).Infof("Action finished")
  125. return true
  126. }
  127. type StepParseArgs struct{}
  128. func (e StepParseArgs) Exec(req *ExecutionRequest) bool {
  129. var err error
  130. req.finalParsedCommand, err = parseActionArguments(req.action.Shell, req.Arguments, req.action)
  131. if err != nil {
  132. req.logEntry.ExitCode = -1337
  133. req.logEntry.Stderr = ""
  134. req.logEntry.Stdout = err.Error()
  135. log.Warnf(err.Error())
  136. return false
  137. }
  138. return true
  139. }
  140. type StepExec struct{}
  141. func (e StepExec) Exec(req *ExecutionRequest) bool {
  142. ctx, cancel := context.WithTimeout(context.Background(), time.Duration(req.action.Timeout)*time.Second)
  143. defer cancel()
  144. cmd := exec.CommandContext(ctx, "sh", "-c", req.finalParsedCommand)
  145. stdout, stderr := cmd.Output()
  146. if stderr != nil {
  147. req.logEntry.Stderr = stderr.Error()
  148. }
  149. if ctx.Err() == context.DeadlineExceeded {
  150. req.logEntry.TimedOut = true
  151. }
  152. req.logEntry.ExitCode = int32(cmd.ProcessState.ExitCode())
  153. req.logEntry.Stdout = string(stdout)
  154. return true
  155. }
  156. func parseActionArguments(rawShellCommand string, values map[string]string, action *config.Action) (string, error) {
  157. log.WithFields(log.Fields{
  158. "cmd": rawShellCommand,
  159. }).Infof("Before Parse Args")
  160. r := regexp.MustCompile("{{ *?([a-z]+?) *?}}")
  161. matches := r.FindAllStringSubmatch(rawShellCommand, -1)
  162. for _, match := range matches {
  163. argValue, argProvided := values[match[1]]
  164. if !argProvided {
  165. log.Infof("%v", values)
  166. return "", errors.New("Required arg not provided: " + match[1])
  167. }
  168. err := typecheckActionArgument(match[1], argValue, action)
  169. if err != nil {
  170. return "", err
  171. }
  172. log.WithFields(log.Fields{
  173. "name": match[1],
  174. "value": argValue,
  175. }).Debugf("Arg assigned")
  176. rawShellCommand = strings.Replace(rawShellCommand, match[0], argValue, -1)
  177. }
  178. log.WithFields(log.Fields{
  179. "cmd": rawShellCommand,
  180. }).Infof("After Parse Args")
  181. return rawShellCommand, nil
  182. }
  183. func typecheckActionArgument(name string, value string, action *config.Action) error {
  184. arg := findArg(name, action)
  185. if arg == nil {
  186. return errors.New("Action arg not defined: " + name)
  187. }
  188. if len(arg.Choices) > 0 {
  189. return typecheckChoice(value, arg)
  190. }
  191. return TypeSafetyCheck(name, value, arg.Type)
  192. }
  193. func typecheckChoice(value string, arg *config.ActionArgument) error {
  194. for _, choice := range arg.Choices {
  195. if value == choice.Value {
  196. return nil
  197. }
  198. }
  199. return errors.New("Arg value is not one of the predefined choices")
  200. }
  201. func TypeSafetyCheck(name string, value string, typ string) error {
  202. pattern, found := typecheckRegex[typ]
  203. log.Infof("%v %v", pattern, typ)
  204. if !found {
  205. return errors.New("Arg type not implemented " + typ)
  206. }
  207. matches, _ := regexp.MatchString(pattern, value)
  208. if !matches {
  209. log.WithFields(log.Fields{
  210. "name": name,
  211. "type": typ,
  212. "value": value,
  213. }).Warn("Arg type check safety failure")
  214. return errors.New("Invalid argument, doesn't match " + typ)
  215. }
  216. return nil
  217. }
  218. func findArg(name string, action *config.Action) *config.ActionArgument {
  219. for _, arg := range action.Arguments {
  220. if arg.Name == name {
  221. return &arg
  222. }
  223. }
  224. return nil
  225. }