grpcApi.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  1. package grpcapi
  2. import (
  3. ctx "context"
  4. apiv1 "github.com/OliveTin/OliveTin/gen/grpc/olivetin/api/v1"
  5. "github.com/google/uuid"
  6. log "github.com/sirupsen/logrus"
  7. "google.golang.org/genproto/googleapis/api/httpbody"
  8. "google.golang.org/grpc"
  9. "google.golang.org/grpc/codes"
  10. "google.golang.org/grpc/metadata"
  11. "google.golang.org/grpc/status"
  12. "fmt"
  13. "net"
  14. acl "github.com/OliveTin/OliveTin/internal/acl"
  15. config "github.com/OliveTin/OliveTin/internal/config"
  16. executor "github.com/OliveTin/OliveTin/internal/executor"
  17. installationinfo "github.com/OliveTin/OliveTin/internal/installationinfo"
  18. sv "github.com/OliveTin/OliveTin/internal/stringvariables"
  19. )
  20. var (
  21. cfg *config.Config
  22. )
  23. type oliveTinAPI struct {
  24. // Uncomment this if you want to allow undefined methods during dev.
  25. // apiv1.UnimplementedOliveTinApiServiceServer
  26. executor *executor.Executor
  27. }
  28. func (api *oliveTinAPI) KillAction(ctx ctx.Context, req *apiv1.KillActionRequest) (*apiv1.KillActionResponse, error) {
  29. ret := &apiv1.KillActionResponse{
  30. ExecutionTrackingId: req.ExecutionTrackingId,
  31. }
  32. var execReqLogEntry *executor.InternalLogEntry
  33. execReqLogEntry, ret.Found = api.executor.GetLog(req.ExecutionTrackingId)
  34. if !ret.Found {
  35. log.Warnf("Killing execution request not possible - not found by tracking ID: %v", req.ExecutionTrackingId)
  36. return ret, nil
  37. }
  38. log.Warnf("Killing execution request by tracking ID: %v", req.ExecutionTrackingId)
  39. action := cfg.FindAction(execReqLogEntry.ActionTitle)
  40. if action == nil {
  41. log.Warnf("Killing execution request not possible - action not found: %v", execReqLogEntry.ActionTitle)
  42. ret.Killed = false
  43. return ret, nil
  44. }
  45. user := acl.UserFromContext(ctx, cfg)
  46. api.killActionByTrackingId(user, action, execReqLogEntry, ret)
  47. return ret, nil
  48. }
  49. func (api *oliveTinAPI) killActionByTrackingId(user *acl.AuthenticatedUser, action *config.Action, execReqLogEntry *executor.InternalLogEntry, ret *apiv1.KillActionResponse) {
  50. if !acl.IsAllowedKill(cfg, user, action) {
  51. log.Warnf("Killing execution request not possible - user not allowed to kill this action: %v", execReqLogEntry.ExecutionTrackingID)
  52. ret.Killed = false
  53. }
  54. err := api.executor.Kill(execReqLogEntry)
  55. if err != nil {
  56. log.Warnf("Killing execution request err: %v", err)
  57. ret.AlreadyCompleted = true
  58. ret.Killed = false
  59. } else {
  60. ret.Killed = true
  61. }
  62. }
  63. func (api *oliveTinAPI) StartAction(ctx ctx.Context, req *apiv1.StartActionRequest) (*apiv1.StartActionResponse, error) {
  64. args := make(map[string]string)
  65. for _, arg := range req.Arguments {
  66. args[arg.Name] = arg.Value
  67. }
  68. api.executor.MapActionIdToBindingLock.RLock()
  69. pair := api.executor.MapActionIdToBinding[req.ActionId]
  70. api.executor.MapActionIdToBindingLock.RUnlock()
  71. if pair == nil || pair.Action == nil {
  72. return nil, status.Errorf(codes.NotFound, "Action not found.")
  73. }
  74. authenticatedUser := acl.UserFromContext(ctx, cfg)
  75. execReq := executor.ExecutionRequest{
  76. Action: pair.Action,
  77. EntityPrefix: pair.EntityPrefix,
  78. TrackingID: req.UniqueTrackingId,
  79. Arguments: args,
  80. AuthenticatedUser: authenticatedUser,
  81. Cfg: cfg,
  82. }
  83. api.executor.ExecRequest(&execReq)
  84. return &apiv1.StartActionResponse{
  85. ExecutionTrackingId: execReq.TrackingID,
  86. }, nil
  87. }
  88. func (api *oliveTinAPI) PasswordHash(ctx ctx.Context, req *apiv1.PasswordHashRequest) (*httpbody.HttpBody, error) {
  89. hash, err := createHash(req.Password)
  90. if err != nil {
  91. return nil, status.Errorf(codes.Internal, "Error creating hash.")
  92. }
  93. ret := &httpbody.HttpBody{
  94. ContentType: "text/plain",
  95. Data: []byte("Your password hash is: " + hash),
  96. }
  97. return ret, nil
  98. }
  99. func (api *oliveTinAPI) LocalUserLogin(ctx ctx.Context, req *apiv1.LocalUserLoginRequest) (*apiv1.LocalUserLoginResponse, error) {
  100. match := checkUserPassword(cfg, req.Username, req.Password)
  101. if match {
  102. grpc.SendHeader(ctx, metadata.Pairs("set-username", req.Username))
  103. log.WithFields(log.Fields{
  104. "username": req.Username,
  105. }).Info("LocalUserLogin: User logged in successfully.")
  106. } else {
  107. log.WithFields(log.Fields{
  108. "username": req.Username,
  109. }).Warn("LocalUserLogin: User login failed.")
  110. }
  111. return &apiv1.LocalUserLoginResponse{
  112. Success: match,
  113. }, nil
  114. }
  115. func (api *oliveTinAPI) StartActionAndWait(ctx ctx.Context, req *apiv1.StartActionAndWaitRequest) (*apiv1.StartActionAndWaitResponse, error) {
  116. args := make(map[string]string)
  117. for _, arg := range req.Arguments {
  118. args[arg.Name] = arg.Value
  119. }
  120. user := acl.UserFromContext(ctx, cfg)
  121. execReq := executor.ExecutionRequest{
  122. Action: api.executor.FindActionBindingByID(req.ActionId),
  123. TrackingID: uuid.NewString(),
  124. Arguments: args,
  125. AuthenticatedUser: user,
  126. Cfg: cfg,
  127. }
  128. wg, _ := api.executor.ExecRequest(&execReq)
  129. wg.Wait()
  130. internalLogEntry, ok := api.executor.GetLog(execReq.TrackingID)
  131. if ok {
  132. return &apiv1.StartActionAndWaitResponse{
  133. LogEntry: internalLogEntryToPb(internalLogEntry, user),
  134. }, nil
  135. } else {
  136. return nil, fmt.Errorf("execution not found")
  137. }
  138. }
  139. func (api *oliveTinAPI) StartActionByGet(ctx ctx.Context, req *apiv1.StartActionByGetRequest) (*apiv1.StartActionByGetResponse, error) {
  140. args := make(map[string]string)
  141. execReq := executor.ExecutionRequest{
  142. Action: api.executor.FindActionBindingByID(req.ActionId),
  143. TrackingID: uuid.NewString(),
  144. Arguments: args,
  145. AuthenticatedUser: acl.UserFromContext(ctx, cfg),
  146. Cfg: cfg,
  147. }
  148. _, uniqueTrackingId := api.executor.ExecRequest(&execReq)
  149. return &apiv1.StartActionByGetResponse{
  150. ExecutionTrackingId: uniqueTrackingId,
  151. }, nil
  152. }
  153. func (api *oliveTinAPI) StartActionByGetAndWait(ctx ctx.Context, req *apiv1.StartActionByGetAndWaitRequest) (*apiv1.StartActionByGetAndWaitResponse, error) {
  154. args := make(map[string]string)
  155. user := acl.UserFromContext(ctx, cfg)
  156. execReq := executor.ExecutionRequest{
  157. Action: api.executor.FindActionBindingByID(req.ActionId),
  158. TrackingID: uuid.NewString(),
  159. Arguments: args,
  160. AuthenticatedUser: user,
  161. Cfg: cfg,
  162. }
  163. wg, _ := api.executor.ExecRequest(&execReq)
  164. wg.Wait()
  165. internalLogEntry, ok := api.executor.GetLog(execReq.TrackingID)
  166. if ok {
  167. return &apiv1.StartActionByGetAndWaitResponse{
  168. LogEntry: internalLogEntryToPb(internalLogEntry, user),
  169. }, nil
  170. } else {
  171. return nil, status.Errorf(codes.NotFound, "Execution not found.")
  172. }
  173. }
  174. func internalLogEntryToPb(logEntry *executor.InternalLogEntry, authenticatedUser *acl.AuthenticatedUser) *apiv1.LogEntry {
  175. pble := &apiv1.LogEntry{
  176. ActionTitle: logEntry.ActionTitle,
  177. ActionIcon: logEntry.ActionIcon,
  178. ActionId: logEntry.ActionId,
  179. DatetimeStarted: logEntry.DatetimeStarted.Format("2006-01-02 15:04:05"),
  180. DatetimeFinished: logEntry.DatetimeFinished.Format("2006-01-02 15:04:05"),
  181. DatetimeIndex: logEntry.Index,
  182. Output: logEntry.Output,
  183. TimedOut: logEntry.TimedOut,
  184. Blocked: logEntry.Blocked,
  185. ExitCode: logEntry.ExitCode,
  186. Tags: logEntry.Tags,
  187. ExecutionTrackingId: logEntry.ExecutionTrackingID,
  188. ExecutionStarted: logEntry.ExecutionStarted,
  189. ExecutionFinished: logEntry.ExecutionFinished,
  190. User: logEntry.Username,
  191. }
  192. if !pble.ExecutionFinished {
  193. pble.CanKill = acl.IsAllowedKill(cfg, authenticatedUser, cfg.FindAction(logEntry.ActionTitle))
  194. }
  195. return pble
  196. }
  197. func getExecutionStatusByTrackingID(api *oliveTinAPI, executionTrackingId string) *executor.InternalLogEntry {
  198. logEntry, ok := api.executor.GetLog(executionTrackingId)
  199. if !ok {
  200. return nil
  201. }
  202. return logEntry
  203. }
  204. func getMostRecentExecutionStatusById(api *oliveTinAPI, actionId string) *executor.InternalLogEntry {
  205. var ile *executor.InternalLogEntry
  206. logs := api.executor.GetLogsByActionId(actionId)
  207. if len(logs) == 0 {
  208. return nil
  209. } else {
  210. // Get last log entry
  211. ile = logs[len(logs)-1]
  212. }
  213. return ile
  214. }
  215. func (api *oliveTinAPI) ExecutionStatus(ctx ctx.Context, req *apiv1.ExecutionStatusRequest) (*apiv1.ExecutionStatusResponse, error) {
  216. res := &apiv1.ExecutionStatusResponse{}
  217. user := acl.UserFromContext(ctx, cfg)
  218. var ile *executor.InternalLogEntry
  219. if req.ExecutionTrackingId != "" {
  220. ile = getExecutionStatusByTrackingID(api, req.ExecutionTrackingId)
  221. } else {
  222. ile = getMostRecentExecutionStatusById(api, req.ActionId)
  223. }
  224. if ile == nil {
  225. return nil, status.Error(codes.NotFound, "Execution not found")
  226. } else {
  227. res.LogEntry = internalLogEntryToPb(ile, user)
  228. }
  229. return res, nil
  230. }
  231. /**
  232. func (api *oliveTinAPI) WatchExecution(req *apiv1.WatchExecutionRequest, srv apiv1.OliveTinApi_WatchExecutionServer) error {
  233. log.Infof("Watch")
  234. if logEntry, ok := api.executor.Logs[req.ExecutionUuid]; !ok {
  235. log.Errorf("Execution not found: %v", req.ExecutionUuid)
  236. return nil
  237. } else {
  238. if logEntry.ExecutionStarted {
  239. for !logEntry.ExecutionCompleted {
  240. tmp := make([]byte, 256)
  241. red, err := io.ReadAtLeast(logEntry.StdoutBuffer, tmp, 1)
  242. log.Infof("%v %v", red, err)
  243. srv.Send(&apiv1.WatchExecutionUpdate{
  244. Update: string(tmp),
  245. })
  246. }
  247. }
  248. return nil
  249. }
  250. }
  251. */
  252. func (api *oliveTinAPI) Logout(ctx ctx.Context, req *apiv1.LogoutRequest) (*httpbody.HttpBody, error) {
  253. user := acl.UserFromContext(ctx, cfg)
  254. grpc.SendHeader(ctx, metadata.Pairs("logout-provider", user.Provider))
  255. grpc.SendHeader(ctx, metadata.Pairs("logout-sid", user.SID))
  256. return nil, nil
  257. }
  258. func (api *oliveTinAPI) GetDashboardComponents(ctx ctx.Context, req *apiv1.GetDashboardComponentsRequest) (*apiv1.GetDashboardComponentsResponse, error) {
  259. user := acl.UserFromContext(ctx, cfg)
  260. if user.IsGuest() && cfg.AuthRequireGuestsToLogin {
  261. return nil, status.Errorf(codes.PermissionDenied, "Guests are not allowed to access the dashboard.")
  262. }
  263. res := buildDashboardResponse(api.executor, cfg, user)
  264. if len(res.Actions) == 0 {
  265. log.WithFields(log.Fields{
  266. "username": user.Username,
  267. "usergroupLine": user.UsergroupLine,
  268. "provider": user.Provider,
  269. "acls": user.Acls,
  270. "availableActions": len(cfg.Actions),
  271. }).Warn("Zero actions found for user")
  272. }
  273. log.Tracef("GetDashboardComponents: %v", res)
  274. return res, nil
  275. }
  276. func (api *oliveTinAPI) GetLogs(ctx ctx.Context, req *apiv1.GetLogsRequest) (*apiv1.GetLogsResponse, error) {
  277. user := acl.UserFromContext(ctx, cfg)
  278. ret := &apiv1.GetLogsResponse{}
  279. logEntries, countRemaining := api.executor.GetLogTrackingIds(req.StartOffset, cfg.LogHistoryPageSize)
  280. for _, logEntry := range logEntries {
  281. action := cfg.FindAction(logEntry.ActionTitle)
  282. if action == nil || acl.IsAllowedLogs(cfg, user, action) {
  283. pbLogEntry := internalLogEntryToPb(logEntry, user)
  284. ret.Logs = append(ret.Logs, pbLogEntry)
  285. }
  286. }
  287. ret.CountRemaining = countRemaining
  288. ret.PageSize = cfg.LogHistoryPageSize
  289. return ret, nil
  290. }
  291. /*
  292. This function is ONLY a helper for the UI - the arguments are validated properly
  293. on the StartAction -> Executor chain. This is here basically to provide helpful
  294. error messages more quickly before starting the action.
  295. */
  296. func (api *oliveTinAPI) ValidateArgumentType(ctx ctx.Context, req *apiv1.ValidateArgumentTypeRequest) (*apiv1.ValidateArgumentTypeResponse, error) {
  297. err := executor.TypeSafetyCheck("", req.Value, req.Type)
  298. desc := ""
  299. if err != nil {
  300. desc = err.Error()
  301. }
  302. return &apiv1.ValidateArgumentTypeResponse{
  303. Valid: err == nil,
  304. Description: desc,
  305. }, nil
  306. }
  307. func (api *oliveTinAPI) WhoAmI(ctx ctx.Context, req *apiv1.WhoAmIRequest) (*apiv1.WhoAmIResponse, error) {
  308. user := acl.UserFromContext(ctx, cfg)
  309. res := &apiv1.WhoAmIResponse{
  310. AuthenticatedUser: user.Username,
  311. Usergroup: user.UsergroupLine,
  312. Provider: user.Provider,
  313. Sid: user.SID,
  314. Acls: user.Acls,
  315. }
  316. return res, nil
  317. }
  318. func (api *oliveTinAPI) SosReport(ctx ctx.Context, req *apiv1.SosReportRequest) (*httpbody.HttpBody, error) {
  319. sos := installationinfo.GetSosReport()
  320. if !cfg.InsecureAllowDumpSos {
  321. log.Info(sos)
  322. sos = "Your SOS Report has been logged to OliveTin logs.\n\nIf you are in a safe network, you can temporarily set `insecureAllowDumpSos: true` in your config.yaml, restart OliveTin, and refresh this page - it will put the output directly in the browser."
  323. }
  324. ret := &httpbody.HttpBody{
  325. ContentType: "text/plain",
  326. Data: []byte(sos),
  327. }
  328. return ret, nil
  329. }
  330. func (api *oliveTinAPI) DumpVars(ctx ctx.Context, req *apiv1.DumpVarsRequest) (*apiv1.DumpVarsResponse, error) {
  331. res := &apiv1.DumpVarsResponse{}
  332. if !cfg.InsecureAllowDumpVars {
  333. res.Alert = "Dumping variables is not allowed by default because it is insecure."
  334. return res, nil
  335. }
  336. res.Alert = "Dumping variables has been enabled in the configuration. Please set InsecureAllowDumpVars = false again after you don't need it anymore"
  337. res.Contents = sv.GetAll()
  338. return res, nil
  339. }
  340. func (api *oliveTinAPI) DumpPublicIdActionMap(ctx ctx.Context, req *apiv1.DumpPublicIdActionMapRequest) (*apiv1.DumpPublicIdActionMapResponse, error) {
  341. res := &apiv1.DumpPublicIdActionMapResponse{}
  342. res.Contents = make(map[string]*apiv1.ActionEntityPair)
  343. if !cfg.InsecureAllowDumpActionMap {
  344. res.Alert = "Dumping Public IDs is disallowed."
  345. return res, nil
  346. }
  347. api.executor.MapActionIdToBindingLock.RLock()
  348. for k, v := range api.executor.MapActionIdToBinding {
  349. res.Contents[k] = &apiv1.ActionEntityPair{
  350. ActionTitle: v.Action.Title,
  351. EntityPrefix: v.EntityPrefix,
  352. }
  353. }
  354. api.executor.MapActionIdToBindingLock.RUnlock()
  355. res.Alert = "Dumping variables has been enabled in the configuration. Please set InsecureAllowDumpActionMap = false again after you don't need it anymore"
  356. return res, nil
  357. }
  358. func (api *oliveTinAPI) GetReadyz(ctx ctx.Context, req *apiv1.GetReadyzRequest) (*apiv1.GetReadyzResponse, error) {
  359. res := &apiv1.GetReadyzResponse{
  360. Status: "OK",
  361. }
  362. return res, nil
  363. }
  364. // Start will start the GRPC API.
  365. func Start(globalConfig *config.Config, ex *executor.Executor) {
  366. cfg = globalConfig
  367. log.WithFields(log.Fields{
  368. "address": cfg.ListenAddressGrpcActions,
  369. }).Info("Starting gRPC API")
  370. lis, err := net.Listen("tcp", cfg.ListenAddressGrpcActions)
  371. if err != nil {
  372. log.Fatalf("Failed to listen - %v", err)
  373. }
  374. grpcServer := grpc.NewServer()
  375. apiv1.RegisterOliveTinApiServiceServer(grpcServer, newServer(ex))
  376. err = grpcServer.Serve(lis)
  377. if err != nil {
  378. log.Fatalf("Could not start gRPC Server - %v", err)
  379. }
  380. }
  381. func newServer(ex *executor.Executor) *oliveTinAPI {
  382. server := oliveTinAPI{}
  383. server.executor = ex
  384. return &server
  385. }