api_justification_test.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. package api
  2. import (
  3. "context"
  4. "testing"
  5. "time"
  6. "connectrpc.com/connect"
  7. "github.com/google/uuid"
  8. "github.com/stretchr/testify/assert"
  9. "github.com/stretchr/testify/require"
  10. apiv1 "github.com/OliveTin/OliveTin/gen/olivetin/api/v1"
  11. "github.com/OliveTin/OliveTin/internal/auth"
  12. config "github.com/OliveTin/OliveTin/internal/config"
  13. "github.com/OliveTin/OliveTin/internal/executor"
  14. )
  15. func TestStartActionRequiresJustificationForGuest(t *testing.T) {
  16. cfg := config.DefaultConfig()
  17. action := &config.Action{
  18. Title: "Send email",
  19. ID: "send_email",
  20. Justification: config.JustificationRequiredNoTemplate,
  21. Shell: "echo done",
  22. }
  23. cfg.Actions = append(cfg.Actions, action)
  24. ex := executor.DefaultExecutor(cfg)
  25. ex.RebuildActionMap()
  26. binding := ex.FindBindingWithNoEntity(action)
  27. require.NotNil(t, binding)
  28. ts, client := getNewTestServerAndClientWithExecutor(cfg, ex)
  29. defer ts.Close()
  30. _, err := client.StartAction(context.Background(), connect.NewRequest(&apiv1.StartActionRequest{
  31. BindingId: binding.ID,
  32. UniqueTrackingId: uuid.NewString(),
  33. }))
  34. require.Error(t, err)
  35. assert.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err))
  36. resp, err := client.StartAction(context.Background(), connect.NewRequest(&apiv1.StartActionRequest{
  37. BindingId: binding.ID,
  38. UniqueTrackingId: uuid.NewString(),
  39. Justification: "New user registration foo@example.com",
  40. }))
  41. require.NoError(t, err)
  42. require.NotEmpty(t, resp.Msg.ExecutionTrackingId)
  43. time.Sleep(200 * time.Millisecond)
  44. entry, ok := ex.GetLog(resp.Msg.ExecutionTrackingId)
  45. require.True(t, ok)
  46. assert.Equal(t, "New user registration foo@example.com", entry.Justification)
  47. }
  48. func TestBuildActionExposesJustificationTemplate(t *testing.T) {
  49. cfg := config.DefaultConfig()
  50. action := &config.Action{
  51. Title: "Audited action",
  52. ID: "audited",
  53. Justification: "{{ target }}",
  54. Shell: "echo hi",
  55. }
  56. cfg.Actions = append(cfg.Actions, action)
  57. ex := executor.DefaultExecutor(cfg)
  58. ex.RebuildActionMap()
  59. binding := ex.FindBindingWithNoEntity(action)
  60. require.NotNil(t, binding)
  61. pb := buildAction(binding, &DashboardRenderRequest{
  62. cfg: cfg,
  63. ex: ex,
  64. })
  65. require.NotNil(t, pb)
  66. assert.Equal(t, "{{ target }}", pb.Justification)
  67. }
  68. func TestBuildActionExposesBlankRequiredJustification(t *testing.T) {
  69. cfg := config.DefaultConfig()
  70. action := &config.Action{
  71. Title: "Audited action",
  72. ID: "audited",
  73. Justification: config.JustificationRequiredNoTemplate,
  74. Shell: "echo hi",
  75. }
  76. cfg.Actions = append(cfg.Actions, action)
  77. ex := executor.DefaultExecutor(cfg)
  78. ex.RebuildActionMap()
  79. binding := ex.FindBindingWithNoEntity(action)
  80. require.NotNil(t, binding)
  81. pb := buildAction(binding, &DashboardRenderRequest{
  82. cfg: cfg,
  83. ex: ex,
  84. })
  85. require.NotNil(t, pb)
  86. assert.Equal(t, config.JustificationRequiredNoTemplate, pb.Justification)
  87. }
  88. func TestResolveStartJustificationUsesTemplateWhenClientValueEmpty(t *testing.T) {
  89. action := &config.Action{
  90. Justification: "{{ ansible_host }}",
  91. }
  92. binding := &executor.ActionBinding{}
  93. got := resolveStartJustification(action, binding, "", map[string]string{
  94. "ansible_host": "192.168.66.8",
  95. })
  96. assert.Equal(t, "192.168.66.8", got)
  97. }
  98. func TestResolveStartJustificationPrefersClientValue(t *testing.T) {
  99. action := &config.Action{
  100. Justification: "{{ ansible_host }}",
  101. }
  102. binding := &executor.ActionBinding{}
  103. got := resolveStartJustification(action, binding, "manual reason", map[string]string{
  104. "ansible_host": "192.168.66.8",
  105. })
  106. assert.Equal(t, "manual reason", got)
  107. }
  108. func TestStartActionResolvesJustificationTemplateForGuest(t *testing.T) {
  109. cfg := config.DefaultConfig()
  110. action := &config.Action{
  111. Title: "Run playbook",
  112. ID: "run_playbook",
  113. Justification: "{{ ansible_host }}",
  114. Shell: "echo done",
  115. Arguments: []config.ActionArgument{
  116. {Name: "ansible_host", Title: "Host"},
  117. },
  118. }
  119. cfg.Actions = append(cfg.Actions, action)
  120. ex := executor.DefaultExecutor(cfg)
  121. ex.RebuildActionMap()
  122. binding := ex.FindBindingWithNoEntity(action)
  123. require.NotNil(t, binding)
  124. ts, client := getNewTestServerAndClientWithExecutor(cfg, ex)
  125. defer ts.Close()
  126. resp, err := client.StartAction(context.Background(), connect.NewRequest(&apiv1.StartActionRequest{
  127. BindingId: binding.ID,
  128. UniqueTrackingId: uuid.NewString(),
  129. Arguments: []*apiv1.StartActionArgument{
  130. {Name: "ansible_host", Value: "stuffbox"},
  131. },
  132. }))
  133. require.NoError(t, err)
  134. require.NotEmpty(t, resp.Msg.ExecutionTrackingId)
  135. time.Sleep(200 * time.Millisecond)
  136. entry, ok := ex.GetLog(resp.Msg.ExecutionTrackingId)
  137. require.True(t, ok)
  138. assert.Equal(t, "stuffbox", entry.Justification)
  139. }
  140. func TestValidateJustificationRequiredAllowsSystemUser(t *testing.T) {
  141. cfg := config.DefaultConfig()
  142. action := &config.Action{Title: "Cron job", Justification: config.JustificationRequiredNoTemplate}
  143. err := validateJustificationRequired(action, "", auth.UserFromSystem(cfg, "cron"))
  144. require.NoError(t, err)
  145. }