client.go 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422
  1. package semaphore
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "fmt"
  6. "io"
  7. "net/http"
  8. "os"
  9. "strconv"
  10. "strings"
  11. "time"
  12. "git.aetherial.dev/aeth/yosai/pkg/daemon"
  13. "git.aetherial.dev/aeth/yosai/pkg/keytags"
  14. "gopkg.in/yaml.v3"
  15. )
  16. const ProjectsPath = "api/projects"
  17. const ProjectPath = "api/project"
  18. const YosaiProject = "Yosai VPN Sentinel"
  19. const YosaiServerInventory = "Yosai VPN Servers"
  20. const YosaiVpnRotationJob = "VPN Rotation playbook"
  21. const YosaiEnvironment = "VPN Server configuration environment variables"
  22. type SemaphoreConnection struct {
  23. Client *http.Client
  24. Keyring daemon.DaemonKeyRing
  25. KeyTagger keytags.Keytagger
  26. Config *daemon.Configuration
  27. ServerUrl string
  28. HttpProto string
  29. ProjectId int
  30. }
  31. type TaskInfo struct {
  32. ID int `json:"id"`
  33. TemplateID int `json:"template_id"`
  34. Status string `json:"status"`
  35. Debug bool `json:"debug"`
  36. Playbook string `json:"playbook"`
  37. Environment string `json:"environment"`
  38. Secret string `json:"secret"`
  39. Limit string `json:"limit"`
  40. }
  41. type TaskOutput struct {
  42. TaskID int `json:"task_id"`
  43. Task string `json:"task"`
  44. Time time.Time `json:"time"`
  45. Output string `json:"output"`
  46. }
  47. type NewTemplateRequest struct {
  48. ProjectId int `json:"project_id"`
  49. Name string `json:"name"`
  50. InventoryId int `json:"inventory_id"`
  51. RepositoryId int `json:"repository_id"`
  52. EnvironmentId int `json:"environment_id"`
  53. Playbook string `json:"playbook"`
  54. App string `json:"app"`
  55. Type string `json:"type"`
  56. }
  57. type JobTemplate struct {
  58. Id int `json:"id"`
  59. ProjectId int `json:"project_id"`
  60. Name string `json:"name"`
  61. InventoryId int `json:"inventory_id"`
  62. RepositoryId int `json:"repository_id"`
  63. EnvironmentId int `json:"environment_id"`
  64. App string `json:"app"`
  65. Playbook string `json:"playbook"`
  66. }
  67. type StartTaskRequest struct {
  68. TemplateID int `json:"template_id"`
  69. ProjectId int `json:"project_id"`
  70. }
  71. type StartTaskResponse struct {
  72. Id int `json:"id"`
  73. TemplateID int `json:"template_id"`
  74. Debug bool `json:"debug"`
  75. DryRun bool `json:"dry_run"`
  76. Diff bool `json:"diff"`
  77. Playbook string `json:"playbook"`
  78. Environment string `json:"environment"`
  79. Limit string `json:"limit"`
  80. }
  81. type EnvironmentVariables struct {
  82. SecretsProviderUrl string `json:"SECRETS_PROVIDER_URL"`
  83. SecretsProviderApiKey string `json:"SECRETS_PROVIDER_API_KEY"`
  84. }
  85. type AddEnvironmentRequest struct {
  86. Name string `json:"name"`
  87. ProjectID int `json:"project_id"`
  88. Password string `json:"password"`
  89. JSON string `json:"json"`
  90. Env string `json:"env"`
  91. }
  92. type EnvironmentResponse struct {
  93. Id int `json:"id"`
  94. Name string `json:"name"`
  95. ProjectID int `json:"project_id"`
  96. Password string `json:"password"`
  97. JSON string `json:"json"`
  98. Env string `json:"env"`
  99. }
  100. type ProjectsResponse struct {
  101. Id int `json:"id"`
  102. Name string `json:"name"`
  103. Created string `json:"created"`
  104. Alert bool `json:"alert"`
  105. AlertChat string `json:"alert_chat"`
  106. MaxParallelTasks int `json:"max_parallel_tasks"`
  107. }
  108. type NewProjectReqeust struct {
  109. Name string `json:"name"`
  110. Alert bool `json:"alert"`
  111. AlertChat string `json:"alert_chat"`
  112. MaxParallelTasks int `json:"max_parallel_tasks"`
  113. }
  114. type NewRepoRequest struct {
  115. Name string `json:"name"` // name of the project
  116. ProjectId int `json:"project_id"` // the numerical ID of the project as per /api/project/<project id>
  117. GitUrl string `json:"git_url"` // the URL of the git repo (SSH address)
  118. GitBranch string `json:"git_branch"` // the branch to pull down
  119. SshKeyId int `json:"ssh_key_id"` // the numerical ID of the ssh key for the repository, as per /api/project/<project id>/keys
  120. }
  121. type NewRepoResponse struct {
  122. Id int `json:"id"` // the numerical ID assigned to the repo by Semaphore
  123. Name string `json:"name"` // name of the project
  124. ProjectId int `json:"project_id"` // the numerical ID of the project as per /api/project/<project id>
  125. GitUrl string `json:"git_url"` // the URL of the git repo (SSH address)
  126. GitBranch string `json:"git_branch"` // the branch to pull down
  127. SshKeyId int `json:"ssh_key_id"` // the numerical ID of the ssh key for the repository, as per /api/project/<project id>/keys
  128. }
  129. type AddKeyRequest struct {
  130. Name string `json:"name"`
  131. Type string `json:"type"`
  132. ProjectId int `json:"project_id"`
  133. LoginPassword loginPassword `json:"login_password"`
  134. Ssh sshKeyAdd `json:"ssh"`
  135. }
  136. func (a AddKeyRequest) GetPublic() string {
  137. if a.Type == "ssh" {
  138. return a.Ssh.Login
  139. } else {
  140. return a.LoginPassword.Login
  141. }
  142. }
  143. func (a AddKeyRequest) GetSecret() string {
  144. if a.Type == "ssh" {
  145. return a.Ssh.PrivateKey
  146. } else {
  147. return a.LoginPassword.Password
  148. }
  149. }
  150. func (a AddKeyRequest) Prepare() string {
  151. b, err := json.Marshal(a)
  152. if err != nil {
  153. return err.Error()
  154. }
  155. return string(b)
  156. }
  157. func (a AddKeyRequest) GetType() string {
  158. return a.Type
  159. }
  160. type KeyItemResponse struct {
  161. Id int `json:"id"`
  162. Name string `json:"name"`
  163. Type string `json:"type"`
  164. ProjectId int `json:"project_id"`
  165. LoginPassword loginPassword `json:"login_password"`
  166. Ssh sshKeyAdd `json:"ssh"`
  167. }
  168. type NewInventoryRequest struct {
  169. Name string `json:"name"`
  170. ProjectId int `json:"project_id"`
  171. Inventory string `json:"inventory"` // This field is where the YAML inventory file gets put, as a string (not a filepath!)
  172. Type string `json:"type"`
  173. SshKeyId int `json:"ssh_key_id"`
  174. BecomeKeyId int `json:"become_key_id"`
  175. }
  176. type InventoryResponse struct {
  177. Id int `json:"id"`
  178. Inventory string `json:"inventory"`
  179. Name string `json:"name"`
  180. ProjectId int `json:"project_id"`
  181. Type string `json:"type"`
  182. SshKeyId int `json:"ssh_key_id"`
  183. BecomeKeyId int `json:"become_key_id"`
  184. }
  185. /*
  186. ####################################################################
  187. ############ IMPLEMENTING daemon.Key FOR KeyItemResponse ###########
  188. ####################################################################
  189. */
  190. func (k KeyItemResponse) GetPublic() string {
  191. return k.Ssh.Login
  192. }
  193. func (k KeyItemResponse) GetSecret() string {
  194. return k.Ssh.PrivateKey
  195. }
  196. func (k KeyItemResponse) Prepare() string {
  197. return k.Type
  198. }
  199. func (k KeyItemResponse) GetType() string {
  200. return k.Type
  201. }
  202. type loginPassword struct {
  203. Password string `json:"password"`
  204. Login string `json:"login"`
  205. }
  206. type sshKeyAdd struct {
  207. PrivateKey string `json:"private_key"`
  208. Login string `json:"login"`
  209. }
  210. /*
  211. ###################################################################
  212. ########### IMPLEMENTING THE DaemonKeyRing INTERFACE ##############
  213. ###################################################################
  214. */
  215. /*
  216. Get SSH key by its name
  217. */
  218. func (s SemaphoreConnection) GetKey(name string) (daemon.Key, error) {
  219. var key KeyItemResponse
  220. keys, err := s.GetAllKeys()
  221. if err != nil {
  222. return key, daemon.KeyRingError
  223. }
  224. for i := range keys {
  225. if keys[i].Name == name {
  226. return keys[i], nil
  227. }
  228. }
  229. return key, daemon.KeyNotFound
  230. }
  231. /*
  232. Add SSH Key to a project
  233. :param name: the name to assign the key in the project
  234. :param keyring: a daemon.DaemonKeyRing implementer that can return the API key for Semaphore
  235. :param key: a daemon.Key implementer wrapping the SSH key
  236. */
  237. func (s SemaphoreConnection) AddKey(name string, key daemon.Key) error {
  238. _, err := s.GetKeyId(name)
  239. if err == nil { // return if the key exists
  240. return nil
  241. }
  242. path := fmt.Sprintf("%s/%v/keys", ProjectPath, s.ProjectId)
  243. _, err = s.Post(path, bytes.NewReader([]byte(key.Prepare())))
  244. if err != nil {
  245. return err
  246. }
  247. return nil
  248. }
  249. /*
  250. Drop a key from the Semaphore secret store
  251. */
  252. func (s SemaphoreConnection) RemoveKey(name string) error {
  253. _, err := s.Delete(name)
  254. return err
  255. }
  256. // Return the resource name for logging purposes
  257. func (s SemaphoreConnection) Source() string {
  258. return "Semaphore Keystore"
  259. }
  260. // NewKeyRequest builder function
  261. func (s SemaphoreConnection) NewKeyRequestBuilder(name string, key daemon.Key) daemon.Key {
  262. if key.GetType() == "ssh" {
  263. return AddKeyRequest{
  264. Name: name,
  265. Type: key.GetType(),
  266. ProjectId: s.ProjectId,
  267. Ssh: sshKeyAdd{
  268. Login: key.GetPublic(),
  269. PrivateKey: key.GetSecret(),
  270. },
  271. }
  272. } else {
  273. return AddKeyRequest{
  274. Name: name,
  275. Type: key.GetType(),
  276. ProjectId: s.ProjectId,
  277. LoginPassword: loginPassword{
  278. Login: key.GetPublic(),
  279. Password: key.GetSecret(),
  280. },
  281. }
  282. }
  283. }
  284. /*
  285. Create a new semaphore client
  286. :param url: the base url of the semaphore server, without the HTTP/S prefix
  287. :param proto: either HTTP or HTTPS, depending on the server's SSL setup
  288. :param log: an io.Writer to write logfile to
  289. :param keyring: a daemon.DaemonKeyRing implementer to get the Semaphore API key from
  290. */
  291. func NewSemaphoreClient(url string, proto string, keyring daemon.DaemonKeyRing, conf *daemon.Configuration, keytagger keytags.Keytagger) SemaphoreConnection {
  292. client := &http.Client{}
  293. semaphoreBootstrap := SemaphoreConnection{Client: client, ServerUrl: url, HttpProto: proto, Keyring: keyring, KeyTagger: keytagger, Config: conf}
  294. semaphoreBootstrap.Log("Using mode ", proto)
  295. id, err := semaphoreBootstrap.GetProjectByName(YosaiProject)
  296. if err != nil {
  297. semaphoreBootstrap.Log(YosaiProject, "Not found in semaphore. Creating...")
  298. err = semaphoreBootstrap.NewProject(YosaiProject)
  299. if err != nil {
  300. semaphoreBootstrap.Log("Fatal error creating the project in semaphore: ", err.Error(), "exiting.")
  301. os.Exit(127)
  302. }
  303. id, err := semaphoreBootstrap.GetProjectByName(YosaiProject)
  304. if err != nil {
  305. semaphoreBootstrap.Log("Error finding the project: ", YosaiProject, err.Error(), "exiting.")
  306. os.Exit(127)
  307. }
  308. semaphoreBootstrap.Log(YosaiProject, "found with ID: ", fmt.Sprint(id))
  309. semaphoreBootstrap.Log("OK! Semaphore connection established.")
  310. return SemaphoreConnection{
  311. Client: client,
  312. ServerUrl: url,
  313. HttpProto: proto,
  314. ProjectId: id,
  315. Keyring: keyring,
  316. Config: conf,
  317. KeyTagger: keytagger,
  318. }
  319. }
  320. semaphoreBootstrap.Log("OK! Semaphore connection established.")
  321. return SemaphoreConnection{
  322. Client: &http.Client{},
  323. ServerUrl: url,
  324. HttpProto: proto,
  325. ProjectId: id,
  326. Keyring: keyring,
  327. Config: conf,
  328. KeyTagger: keytagger,
  329. }
  330. }
  331. // logging wrapper
  332. func (s *SemaphoreConnection) Log(msg ...string) {
  333. semMsg := []string{
  334. "SemaphoreConnection:",
  335. }
  336. semMsg = append(semMsg, msg...)
  337. s.Config.Log(semMsg...)
  338. }
  339. /*
  340. Create a new 'Project' in Semaphore
  341. :param name: the name to assign the project
  342. :param keyring: a daemon.DaemonKeyRing implementer to get the Semaphore API key from
  343. */
  344. func (s SemaphoreConnection) NewProject(name string) error {
  345. _, err := s.GetProjectByName(name)
  346. if err == nil {
  347. return nil // return nil of project already exists
  348. }
  349. var b []byte
  350. newProj := NewProjectReqeust{
  351. Name: name,
  352. Alert: false,
  353. AlertChat: "",
  354. MaxParallelTasks: 0,
  355. }
  356. b, err = json.Marshal(&newProj)
  357. if err != nil {
  358. return &SemaphoreClientError{Msg: err.Error()}
  359. }
  360. _, err = s.Post(ProjectsPath, bytes.NewReader(b))
  361. if err != nil {
  362. return err
  363. }
  364. return nil
  365. }
  366. /*
  367. Add a repository to the project designated for the Yosai service
  368. :param giturl: the url for the git repo containing the ansible scripts for VPN server config
  369. :param branch: the branch to target on the git repo
  370. */
  371. func (s SemaphoreConnection) AddRepository(giturl string, branch string) error {
  372. _, err := s.GetRepoByName(fmt.Sprintf("%s:%s", giturl, branch))
  373. if err == nil { // return if the repo exists
  374. return nil
  375. }
  376. sshKeyId, err := s.GetKeyId(s.KeyTagger.GitSshKeyname())
  377. if err != nil {
  378. return err
  379. }
  380. repoAddRequest := NewRepoRequest{
  381. Name: fmt.Sprintf("%s:%s", giturl, branch),
  382. ProjectId: s.ProjectId,
  383. GitUrl: giturl,
  384. GitBranch: branch,
  385. SshKeyId: sshKeyId,
  386. }
  387. b, err := json.Marshal(&repoAddRequest)
  388. if err != nil {
  389. return &SemaphoreClientError{Msg: err.Error()}
  390. }
  391. _, err = s.Post(fmt.Sprintf("%s/%v/repositories", ProjectPath, s.ProjectId), bytes.NewReader(b))
  392. if err != nil {
  393. return err
  394. }
  395. return nil
  396. }
  397. /*
  398. Generic POST Request to sent to the Semaphore server
  399. :param path: the path to the API to POST. Preceeding slashes will be trimmed
  400. :param body: an io.Reader implementer to use as the POST body. Must comply with application/json Content-Type
  401. */
  402. func (s SemaphoreConnection) Put(path string, body io.Reader) ([]byte, error) {
  403. var b []byte
  404. apikey, err := s.Keyring.GetKey(s.KeyTagger.SemaphoreApiKeyname())
  405. if err != nil {
  406. return b, &SemaphoreClientError{Msg: err.Error()}
  407. }
  408. req, err := http.NewRequest(http.MethodPut, fmt.Sprintf("%s://%s/%s", s.HttpProto, s.ServerUrl, strings.TrimPrefix(path, "/")), body)
  409. if err != nil {
  410. return b, &SemaphoreClientError{Msg: err.Error()}
  411. }
  412. req.Header.Add("Authorization", apikey.Prepare())
  413. req.Header.Add("Content-Type", "application/json")
  414. resp, err := s.Client.Do(req)
  415. if err != nil {
  416. return b, &SemaphoreClientError{Msg: err.Error()}
  417. }
  418. defer resp.Body.Close()
  419. if resp.StatusCode >= 400 {
  420. return b, &SemaphoreClientError{Msg: resp.Status}
  421. }
  422. b, err = io.ReadAll(resp.Body)
  423. if err != nil {
  424. return b, &SemaphoreClientError{Msg: err.Error()}
  425. }
  426. return b, nil
  427. }
  428. /*
  429. Generic POST Request to sent to the Semaphore server
  430. :param path: the path to the API to POST. Preceeding slashes will be trimmed
  431. :param body: an io.Reader implementer to use as the POST body. Must comply with application/json Content-Type
  432. */
  433. func (s SemaphoreConnection) Post(path string, body io.Reader) ([]byte, error) {
  434. var b []byte
  435. apikey, err := s.Keyring.GetKey(s.KeyTagger.SemaphoreApiKeyname())
  436. if err != nil {
  437. return b, &SemaphoreClientError{Msg: err.Error()}
  438. }
  439. req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s://%s/%s", s.HttpProto, s.ServerUrl, strings.TrimPrefix(path, "/")), body)
  440. if err != nil {
  441. return b, &SemaphoreClientError{Msg: err.Error()}
  442. }
  443. req.Header.Add("Authorization", apikey.Prepare())
  444. req.Header.Add("Content-Type", "application/json")
  445. resp, err := s.Client.Do(req)
  446. if err != nil {
  447. return b, &SemaphoreClientError{Msg: err.Error()}
  448. }
  449. defer resp.Body.Close()
  450. b, err = io.ReadAll(resp.Body)
  451. if err != nil {
  452. return b, &SemaphoreClientError{Msg: err.Error()}
  453. }
  454. if resp.StatusCode >= 400 {
  455. return b, &SemaphoreClientError{Msg: resp.Status}
  456. }
  457. return b, nil
  458. }
  459. /*
  460. Agnostic GET method for calling the upstream Semaphore server
  461. :param path: the path to GET, added into the base API url
  462. */
  463. func (s SemaphoreConnection) Get(path string) ([]byte, error) {
  464. var b []byte
  465. apiKey, err := s.Keyring.GetKey(s.KeyTagger.SemaphoreApiKeyname())
  466. if err != nil {
  467. return b, &SemaphoreClientError{Msg: err.Error()}
  468. }
  469. req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s://%s/%s", s.HttpProto, s.ServerUrl, strings.TrimPrefix(path, "/")), nil)
  470. if err != nil {
  471. return b, &SemaphoreClientError{Msg: err.Error()}
  472. }
  473. req.Header.Add("Authorization", apiKey.Prepare())
  474. resp, err := s.Client.Do(req)
  475. if err != nil {
  476. return b, &SemaphoreClientError{Msg: err.Error()}
  477. }
  478. defer resp.Body.Close()
  479. b, err = io.ReadAll(resp.Body)
  480. if err != nil {
  481. return b, &SemaphoreClientError{Msg: err.Error()}
  482. }
  483. return b, nil
  484. }
  485. /*
  486. Generic DELETE method for calling the Semaphore server
  487. */
  488. func (s SemaphoreConnection) Delete(path string) ([]byte, error) {
  489. return []byte{}, nil
  490. }
  491. /*
  492. Retrieve the projects in Semaphore
  493. :param keyring: a daemon.DaemonKeyRing implementer to get the API key from for Semaphore
  494. */
  495. func (s SemaphoreConnection) GetProjects() ([]ProjectsResponse, error) {
  496. var projectsResp []ProjectsResponse
  497. b, err := s.Get(ProjectsPath)
  498. if err != nil {
  499. return projectsResp, err
  500. }
  501. err = json.Unmarshal(b, &projectsResp)
  502. if err != nil {
  503. return projectsResp, &SemaphoreClientError{Msg: err.Error()}
  504. }
  505. return projectsResp, nil
  506. }
  507. /*
  508. Get Project by its name, and return its ID
  509. */
  510. func (s SemaphoreConnection) GetProjectByName(name string) (int, error) {
  511. projects, err := s.GetProjects()
  512. if err != nil {
  513. return 0, err
  514. }
  515. for i := range projects {
  516. if projects[i].Name == name {
  517. return projects[i].Id, nil
  518. }
  519. }
  520. return 0, &SemaphoreClientError{Msg: fmt.Sprintf("Project with name: '%s' not found.", name)}
  521. }
  522. /*
  523. Get SSH Keys from the current project
  524. */
  525. func (s SemaphoreConnection) GetAllKeys() ([]KeyItemResponse, error) {
  526. var keys []KeyItemResponse
  527. b, err := s.Get(fmt.Sprintf("%s/%v/keys", ProjectPath, s.ProjectId))
  528. if err != nil {
  529. return keys, err
  530. }
  531. err = json.Unmarshal(b, &keys)
  532. if err != nil {
  533. return keys, &SemaphoreClientError{Msg: err.Error()}
  534. }
  535. return keys, nil
  536. }
  537. /*
  538. Return a key ID from the Semaphore keystore by it's name
  539. :param keyname: the name of the key in Semaphore
  540. */
  541. func (s SemaphoreConnection) GetKeyId(keyname string) (int, error) {
  542. keys, err := s.GetAllKeys()
  543. if err != nil {
  544. return 0, err
  545. }
  546. for i := range keys {
  547. if keys[i].Name == keyname {
  548. return keys[i].Id, nil
  549. }
  550. }
  551. return 0, &KeyNotFound{Keyname: keyname}
  552. }
  553. /*
  554. Get the output of a task
  555. :param taskId: the ID of the task that was ran
  556. */
  557. func (s SemaphoreConnection) GetTaskOutput(taskId int) ([]TaskOutput, error) {
  558. var taskout []TaskOutput
  559. b, err := s.Get(fmt.Sprintf("%s/%v/tasks/%v/output", ProjectPath, s.ProjectId, taskId))
  560. if err != nil {
  561. return taskout, err
  562. }
  563. err = json.Unmarshal(b, &taskout)
  564. if err != nil {
  565. return taskout, &SemaphoreClientError{Msg: "Could not unmarshall the response from getting task output." + err.Error()}
  566. }
  567. return taskout, nil
  568. }
  569. /*
  570. Get information relating to a task
  571. :param taskId: the ID of the task that was ran
  572. */
  573. func (s SemaphoreConnection) GetTaskInfo(taskId int) (TaskInfo, error) {
  574. var taskout TaskInfo
  575. b, err := s.Get(fmt.Sprintf("%s/%v/tasks/%v", ProjectPath, s.ProjectId, taskId))
  576. if err != nil {
  577. return taskout, err
  578. }
  579. err = json.Unmarshal(b, &taskout)
  580. if err != nil {
  581. return taskout, &SemaphoreClientError{Msg: "Could not unmarshall the response from getting task output." + err.Error()}
  582. }
  583. return taskout, nil
  584. }
  585. /*
  586. Poll for task completion
  587. :param taskId: the ID of the task to be polled
  588. :param max_tries: the number of times to poll the running task before timing out
  589. */
  590. func (s SemaphoreConnection) PollTask(taskId int, max_tries int) error {
  591. var attempts int
  592. for {
  593. attempts = attempts + 1
  594. s.Log("Polling task: ", fmt.Sprint(taskId), " for ", fmt.Sprint(attempts), " times.")
  595. if attempts > max_tries {
  596. s.Log("Polling for job completion timed out after: ", fmt.Sprint(attempts), " attempts.")
  597. return &SemaphoreTimeout{Tries: attempts}
  598. }
  599. resp, err := s.GetTaskInfo(taskId)
  600. if err != nil {
  601. return err
  602. }
  603. s.Log("Job: ", fmt.Sprint(taskId), " is marked with status: ", resp.Status)
  604. if resp.Status == "success" {
  605. return nil
  606. }
  607. if resp.Status == "error" {
  608. return &SemaphoreTimeout{Tries: attempts}
  609. }
  610. time.Sleep(time.Second * 5)
  611. }
  612. }
  613. /*
  614. Add an inventory to semaphore
  615. :param hosts: a list of IP addresses to add to the inventory
  616. */
  617. func (s SemaphoreConnection) AddInventory(name string) error {
  618. _, err := s.GetInventoryByName(name)
  619. if err == nil { // Returning on nil error because that means the inventory exists
  620. return &SemaphoreClientError{Msg: "Inventory Exists! Please update rather than create a new."}
  621. }
  622. sshKeyId, err := s.GetKeyId(s.KeyTagger.VpsSvcAccSshPubkeySeed())
  623. if err != nil {
  624. return err
  625. }
  626. becomeKeyId, err := s.GetKeyId(s.KeyTagger.VpsSvcAccKeyname())
  627. if err != nil {
  628. return &SemaphoreClientError{Msg: err.Error()}
  629. }
  630. inv := YamlInventory{All: yamlInvAll{Hosts: map[string]yamlVars{}}}
  631. b, err := yaml.Marshal(inv)
  632. if err != nil {
  633. s.Config.Log("failed to generate yaml inv stub, using default fallback values. Error: ", err.Error())
  634. b = []byte("all:\n hosts: {}\n")
  635. }
  636. body := NewInventoryRequest{
  637. Name: name,
  638. ProjectId: s.ProjectId,
  639. Inventory: string(b),
  640. SshKeyId: sshKeyId,
  641. BecomeKeyId: becomeKeyId,
  642. Type: "static-yaml",
  643. }
  644. requestBody, err := json.Marshal(&body)
  645. if err != nil {
  646. return &SemaphoreClientError{Msg: err.Error()}
  647. }
  648. _, err = s.Post(fmt.Sprintf("%s/%v/%s", ProjectPath, s.ProjectId, "inventory"), bytes.NewReader(requestBody))
  649. return err
  650. }
  651. /*
  652. Get Inventory by name and return its ID
  653. :param name: the name of the inventory to find
  654. */
  655. func (s SemaphoreConnection) GetInventoryByName(name string) (InventoryResponse, error) {
  656. var out InventoryResponse
  657. resp, err := s.GetAllInventories()
  658. if err != nil {
  659. return out, err
  660. }
  661. for i := range resp {
  662. if resp[i].Name == name {
  663. return resp[i], nil
  664. }
  665. }
  666. return out, &KeyNotFound{Keyname: name}
  667. }
  668. /*
  669. Get all inventories from Semaphore
  670. */
  671. func (s SemaphoreConnection) GetAllInventories() ([]InventoryResponse, error) {
  672. var resp []InventoryResponse
  673. b, err := s.Get(fmt.Sprintf("%s/%v/%s", ProjectPath, s.ProjectId, "inventory"))
  674. if err != nil {
  675. return resp, err
  676. }
  677. err = json.Unmarshal(b, &resp)
  678. if err != nil {
  679. return resp, &SemaphoreClientError{Msg: err.Error()}
  680. }
  681. return resp, nil
  682. }
  683. /*
  684. Update an inventory
  685. */
  686. func (s SemaphoreConnection) UpdateInventory(name string, inv YamlInventory) error {
  687. sshKeyId, err := s.GetKeyId(s.KeyTagger.VpsSvcAccSshPubkeySeed())
  688. if err != nil {
  689. return err
  690. }
  691. becomeKeyId, err := s.GetKeyId(s.KeyTagger.VpsSvcAccKeyname())
  692. if err != nil {
  693. return err
  694. }
  695. b, err := yaml.Marshal(inv)
  696. if err != nil {
  697. return &SemaphoreClientError{Msg: "Error unmarshalling YAML inventory payload: " + err.Error()}
  698. }
  699. targetInv, err := s.GetInventoryByName(name)
  700. if err != nil {
  701. return &SemaphoreClientError{Msg: "Target inventory: " + name + " was not found."}
  702. }
  703. body := InventoryResponse{
  704. Id: targetInv.Id,
  705. Name: name,
  706. ProjectId: s.ProjectId,
  707. Inventory: string(b),
  708. SshKeyId: sshKeyId,
  709. BecomeKeyId: becomeKeyId,
  710. Type: "static-yaml",
  711. }
  712. req, err := json.Marshal(body)
  713. if err != nil {
  714. return &SemaphoreClientError{Msg: "There was an error marshalling the JSON payload: " + err.Error()}
  715. }
  716. _, err = s.Put(fmt.Sprintf("%s/%v/inventory/%v", ProjectPath, s.ProjectId, targetInv.Id), bytes.NewReader(req))
  717. return err
  718. }
  719. /*
  720. Remove host from an inventory
  721. */
  722. func (s SemaphoreConnection) RemoveHostFromInv(name string, host ...string) error {
  723. resp, err := s.GetInventoryByName(name)
  724. if err != nil {
  725. return err
  726. }
  727. var inv YamlInventory
  728. err = yaml.Unmarshal([]byte(resp.Inventory), &inv)
  729. if err != nil {
  730. return &SemaphoreClientError{Msg: "Error unmarshalling inventory from server: " + resp.Inventory + err.Error()}
  731. }
  732. for i := range host {
  733. _, ok := inv.All.Hosts[host[i]]
  734. if !ok {
  735. return &SemaphoreClientError{Msg: "Host: " + host[i] + " not found in the inventory: " + resp.Inventory}
  736. }
  737. delete(inv.All.Hosts, host[i])
  738. }
  739. return s.UpdateInventory(name, inv)
  740. }
  741. /*
  742. Add hosts to inventory
  743. */
  744. func (s SemaphoreConnection) AddHostToInv(name string, host ...daemon.VpnServer) error {
  745. resp, err := s.GetInventoryByName(name)
  746. if err != nil {
  747. return err
  748. }
  749. var inv YamlInventory
  750. err = yaml.Unmarshal([]byte(resp.Inventory), &inv)
  751. if err != nil {
  752. return &SemaphoreClientError{Msg: "Error unmarshalling inventory from server: " + resp.Inventory + err.Error()}
  753. }
  754. newHosts := s.YamlInventoryBuilder(host)
  755. for addr, host := range newHosts.All.Hosts {
  756. inv.All.Hosts[addr] = host
  757. }
  758. return s.UpdateInventory(name, inv)
  759. }
  760. /*
  761. Get a repo ID by its name
  762. :param name: the name of the repo
  763. */
  764. func (s SemaphoreConnection) GetRepoByName(name string) (int, error) {
  765. resp, err := s.GetAllRepos()
  766. if err != nil {
  767. return 0, err
  768. }
  769. for i := range resp {
  770. if resp[i].Name == name {
  771. return resp[i].Id, nil
  772. }
  773. }
  774. return 0, &KeyNotFound{Keyname: name}
  775. }
  776. /*
  777. Get all repositories from Semaphore
  778. */
  779. func (s SemaphoreConnection) GetAllRepos() ([]NewRepoResponse, error) {
  780. var resp []NewRepoResponse
  781. b, err := s.Get(fmt.Sprintf("%s/%v/%s", ProjectPath, s.ProjectId, "repositories"))
  782. if err != nil {
  783. return resp, &SemaphoreClientError{Msg: err.Error()}
  784. }
  785. err = json.Unmarshal(b, &resp)
  786. if err != nil {
  787. return resp, &SemaphoreClientError{Msg: err.Error()}
  788. }
  789. return resp, nil
  790. }
  791. // Create an environment variable configuration, currently unimplemented
  792. func (s SemaphoreConnection) AddEnvironment(envVars EnvironmentVariables) error {
  793. envBytes, err := json.Marshal(envVars)
  794. if err != nil {
  795. return &SemaphoreClientError{Msg: "Couldnt unmarshall the environment variable payload: " + err.Error()}
  796. }
  797. _, err = s.GetEnvironmentId(YosaiEnvironment)
  798. if err == nil {
  799. return nil // environment exists, dont add another with same name
  800. }
  801. var body AddEnvironmentRequest
  802. body = AddEnvironmentRequest{
  803. Name: YosaiEnvironment,
  804. ProjectID: s.ProjectId,
  805. JSON: "{}",
  806. Env: string(envBytes),
  807. }
  808. b, err := json.Marshal(body)
  809. if err != nil {
  810. return &SemaphoreClientError{Msg: "couldnt marshal the JSON payload"}
  811. }
  812. _, err = s.Post(fmt.Sprintf("%s/%v/environment", ProjectPath, s.ProjectId), bytes.NewBuffer(b))
  813. return err
  814. }
  815. // Get an environment configuration ID by name.
  816. func (s SemaphoreConnection) GetEnvironmentId(name string) (int, error) {
  817. var env []EnvironmentResponse
  818. b, err := s.Get(fmt.Sprintf("%s/%v/environment", ProjectPath, s.ProjectId))
  819. if err != nil {
  820. return 0, err
  821. }
  822. err = json.Unmarshal(b, &env)
  823. if err != nil {
  824. return 0, &SemaphoreClientError{Msg: "Couldnt unmarshall the response"}
  825. }
  826. for i := range env {
  827. if env[i].Name == name {
  828. return env[i].Id, nil
  829. }
  830. }
  831. return 0, &KeyNotFound{Keyname: "Couldnt find environment: " + name}
  832. }
  833. /*
  834. Add job template to the Yosai project on Semaphore
  835. :param playbook: the name of the playbook file
  836. :param repoName: the name of the repo that the playbook belongs to
  837. */
  838. func (s SemaphoreConnection) AddJobTemplate(playbook string, repoName string) error {
  839. _, err := s.JobTemplateByName(YosaiVpnRotationJob)
  840. if err == nil {
  841. return nil // return nil because template exists
  842. }
  843. repoId, err := s.GetRepoByName(repoName)
  844. if err != nil {
  845. return err
  846. }
  847. InventoryItem, err := s.GetInventoryByName(YosaiServerInventory)
  848. if err != nil {
  849. return err
  850. }
  851. envId, err := s.GetEnvironmentId(YosaiEnvironment)
  852. if err != nil {
  853. return err
  854. }
  855. templ := NewTemplateRequest{
  856. ProjectId: s.ProjectId,
  857. Name: YosaiVpnRotationJob,
  858. InventoryId: InventoryItem.Id,
  859. RepositoryId: repoId,
  860. EnvironmentId: envId,
  861. Playbook: playbook,
  862. App: "ansible",
  863. Type: "",
  864. }
  865. b, err := json.Marshal(templ)
  866. if err != nil {
  867. return &SemaphoreClientError{Msg: err.Error()}
  868. }
  869. b, err = s.Post(fmt.Sprintf("%s/%v/%s", ProjectPath, s.ProjectId, "templates"), bytes.NewReader(b))
  870. if err != nil {
  871. return &SemaphoreClientError{Msg: fmt.Sprintf("Error: %s\nServer Response: %s", err.Error(), string(b))}
  872. }
  873. return nil
  874. }
  875. /*
  876. Start a task in Semaphore by the template name
  877. :param name: the name of the job template to start
  878. */
  879. func (s SemaphoreConnection) StartJob(name string) (StartTaskResponse, error) {
  880. var resp StartTaskResponse
  881. template, err := s.JobTemplateByName(name)
  882. if err != nil {
  883. return resp, &SemaphoreClientError{Msg: "Could not start job template: " + name + "Error: " + err.Error()}
  884. }
  885. var jobReq StartTaskRequest
  886. jobReq = StartTaskRequest{
  887. TemplateID: template.Id,
  888. ProjectId: s.ProjectId,
  889. }
  890. b, err := json.Marshal(&jobReq)
  891. if err != nil {
  892. return resp, &SemaphoreClientError{Msg: "Couldnt marshal data into byte array: " + err.Error()}
  893. }
  894. rb, err := s.Post(fmt.Sprintf("%s/%v/tasks", ProjectPath, s.ProjectId), bytes.NewReader(b))
  895. if err != nil {
  896. return resp, err
  897. }
  898. err = json.Unmarshal(rb, &resp)
  899. if err != nil {
  900. return resp, &SemaphoreClientError{Msg: "Couldnt unmarshal the response from semaphore: " + err.Error()}
  901. }
  902. return resp, nil
  903. }
  904. /*
  905. Get a job template ID by name
  906. :param name: the name of the job template ID
  907. */
  908. func (s SemaphoreConnection) GetAllTemplates() ([]JobTemplate, error) {
  909. var jobs []JobTemplate
  910. resp, err := s.Get(fmt.Sprintf("%s/%v/templates", ProjectPath, s.ProjectId))
  911. if err != nil {
  912. return jobs, err
  913. }
  914. err = json.Unmarshal(resp, &jobs)
  915. if err != nil {
  916. return jobs, &SemaphoreClientError{Msg: "Error unmarshalling payload response: " + err.Error()}
  917. }
  918. return jobs, nil
  919. }
  920. /*
  921. Bootstrap the Semaphore environment
  922. */
  923. /*
  924. Get a job template ID by name
  925. :param name: the name of the job template ID
  926. */
  927. func (s SemaphoreConnection) JobTemplateByName(name string) (JobTemplate, error) {
  928. var job JobTemplate
  929. jobs, err := s.GetAllTemplates()
  930. if err != nil {
  931. return job, err
  932. }
  933. for i := range jobs {
  934. if jobs[i].Name == name {
  935. return jobs[i], nil
  936. }
  937. }
  938. return job, &SemaphoreClientError{Msg: "Job with name" + name + "not found"}
  939. }
  940. /*
  941. ##########################################################
  942. ################## DAEMON ROUTE HANDLERS #################
  943. ##########################################################
  944. */
  945. type SemaphoreRequest struct {
  946. Target string `json:"target"`
  947. }
  948. /*
  949. Wrapping the functioanlity of the keyring bootstrapper for top level cleanliness
  950. */
  951. func (s SemaphoreConnection) keyBootstrapper() daemon.SockMessage {
  952. reqKeys := s.KeyTagger.GetAnsibleKeys()
  953. for i := range reqKeys {
  954. kn := reqKeys[i]
  955. key, err := s.Keyring.GetKey(kn)
  956. if err != nil {
  957. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  958. }
  959. err = s.AddKey(kn, s.NewKeyRequestBuilder(kn, key))
  960. if err != nil {
  961. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  962. }
  963. }
  964. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_OK, []byte("Daemon keyring successfuly bootstrapped."))
  965. }
  966. /*
  967. Wrapping the functionality of the Project bootstrapper for top level cleanliness
  968. */
  969. func (s SemaphoreConnection) projectBootstrapper() daemon.SockMessage {
  970. s.Config.Log("Entered Semaphore bootstrapping ...")
  971. err := s.NewProject(YosaiProject)
  972. if err != nil {
  973. s.Config.Log("Error creating the project: ", err.Error())
  974. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  975. }
  976. err = s.AddRepository(s.Config.Repo(), s.Config.Branch())
  977. if err != nil {
  978. s.Config.Log("Error creating the repository: ", err.Error())
  979. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  980. }
  981. hashiKey, err := s.Keyring.GetKey(s.KeyTagger.HashicorpVaultKeyname())
  982. if err != nil {
  983. s.Config.Log("Error getting the hashicorp keyring from the keyring: ", err.Error())
  984. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  985. }
  986. err = s.AddEnvironment(EnvironmentVariables{SecretsProviderUrl: s.Config.SecretsBackendUrl(), SecretsProviderApiKey: hashiKey.GetSecret()})
  987. if err != nil {
  988. s.Config.Log("Error creating the environment: ", err.Error())
  989. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  990. }
  991. err = s.AddJobTemplate(s.Config.PlaybookName(), fmt.Sprintf("%s:%s", s.Config.Repo(), s.Config.Branch()))
  992. if err != nil {
  993. s.Config.Log("Error creating the job template: ", err.Error())
  994. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  995. }
  996. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_OK, []byte("Project successfuly bootstrapped."))
  997. }
  998. /*
  999. Wrapping the inventory bootstrap functionality for top level cleanliness
  1000. */
  1001. func (s SemaphoreConnection) inventoryBootstrapper() daemon.SockMessage {
  1002. err := s.AddInventory(YosaiServerInventory)
  1003. if err != nil {
  1004. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  1005. }
  1006. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_OK, []byte("Inventory successfuly bootstrapped."))
  1007. }
  1008. func (s SemaphoreConnection) BootstrapHandler(msg daemon.SockMessage) daemon.SockMessage {
  1009. bootstrapFuncs := []func() daemon.SockMessage{
  1010. s.keyBootstrapper,
  1011. s.inventoryBootstrapper,
  1012. s.projectBootstrapper,
  1013. }
  1014. successMsg := ""
  1015. for i := range bootstrapFuncs {
  1016. call := bootstrapFuncs[i]
  1017. resp := call()
  1018. if resp.StatusCode != daemon.REQUEST_OK {
  1019. s.Log("Error bootstrapping components: ", resp.Target, string(resp.Body))
  1020. continue
  1021. }
  1022. successMsg = successMsg + resp.StatusMsg + "\n"
  1023. }
  1024. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_OK, []byte(successMsg))
  1025. }
  1026. /*
  1027. Wrapping the add project function in a route friendly interface
  1028. :param msg: a message to parse that was recieved from the daemon socket
  1029. */
  1030. func (s SemaphoreConnection) AddProjectHandler(msg daemon.SockMessage) daemon.SockMessage {
  1031. var req SemaphoreRequest
  1032. err := json.Unmarshal(msg.Body, &req)
  1033. if err != nil {
  1034. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  1035. }
  1036. err = s.NewProject(req.Target)
  1037. if err != nil {
  1038. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  1039. }
  1040. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_OK, []byte("Project: "+req.Target+" successfully added."))
  1041. }
  1042. /*
  1043. Wrapping the show project function in a route friendly interface
  1044. :param msg: a message to parse that was recieved from the daemon socket
  1045. */
  1046. func (s SemaphoreConnection) ShowProjectHandler(msg daemon.SockMessage) daemon.SockMessage {
  1047. proj, err := s.GetProjects()
  1048. if err != nil {
  1049. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  1050. }
  1051. b, err := json.MarshalIndent(proj, " ", " ")
  1052. if err != nil {
  1053. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  1054. }
  1055. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_OK, b)
  1056. }
  1057. /*
  1058. Wrapping the run task function in a route friendly interface
  1059. :param msg: a message to parse that was recieved from the daemon socket
  1060. */
  1061. func (s SemaphoreConnection) RunTaskHandler(msg daemon.SockMessage) daemon.SockMessage {
  1062. var req SemaphoreRequest
  1063. err := json.Unmarshal(msg.Body, &req)
  1064. if err != nil {
  1065. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  1066. }
  1067. resp, err := s.StartJob(req.Target)
  1068. if err != nil {
  1069. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  1070. }
  1071. b, err := json.MarshalIndent(resp, " ", " ")
  1072. if err != nil {
  1073. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  1074. }
  1075. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_OK, b)
  1076. }
  1077. /*
  1078. Wrapping the show task function in a route friendly interface
  1079. :param msg: a message to parse that was recieved from the daemon socket
  1080. */
  1081. func (s SemaphoreConnection) ShowTaskHandler(msg daemon.SockMessage) daemon.SockMessage {
  1082. var req SemaphoreRequest
  1083. err := json.Unmarshal(msg.Body, &req)
  1084. if err != nil {
  1085. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  1086. }
  1087. taskid, err := strconv.Atoi(req.Target)
  1088. if err != nil {
  1089. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  1090. }
  1091. taskout, err := s.GetTaskOutput(taskid)
  1092. if err != nil {
  1093. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  1094. }
  1095. b, err := json.MarshalIndent(taskout, " ", " ")
  1096. if err != nil {
  1097. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  1098. }
  1099. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_OK, b)
  1100. }
  1101. /*
  1102. Wrapping the poll task function in a route friendly interface
  1103. :param msg: a message to parse that was recieved from the daemon socket
  1104. */
  1105. func (s SemaphoreConnection) PollTaskHandler(msg daemon.SockMessage) daemon.SockMessage {
  1106. var req SemaphoreRequest
  1107. err := json.Unmarshal(msg.Body, &req)
  1108. if err != nil {
  1109. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  1110. }
  1111. taskId, err := strconv.Atoi(req.Target)
  1112. if err != nil {
  1113. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_TIMEOUT, []byte(err.Error()))
  1114. }
  1115. err = s.PollTask(taskId, 60)
  1116. if err != nil {
  1117. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_TIMEOUT, []byte(err.Error()))
  1118. }
  1119. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_OK, []byte("Task: "+req.Target+" completed."))
  1120. }
  1121. /*
  1122. Wrapping the show hosts functions in a route friendly interface
  1123. :param msg: a message to parse that was recieved from the daemon socket
  1124. */
  1125. func (s SemaphoreConnection) ShowHostHandler(msg daemon.SockMessage) daemon.SockMessage {
  1126. inv, err := s.GetAllInventories()
  1127. if err != nil {
  1128. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  1129. }
  1130. b, err := json.Marshal(inv)
  1131. if err != nil {
  1132. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  1133. }
  1134. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_OK, b)
  1135. }
  1136. /*
  1137. Wrapping the delete host function in a route friendly interface
  1138. :param msg: a message to parse that was recieved from the daemon socket
  1139. */
  1140. func (s SemaphoreConnection) DeleteHostHandler(msg daemon.SockMessage) daemon.SockMessage {
  1141. var req SemaphoreRequest
  1142. err := json.Unmarshal(msg.Body, &req)
  1143. if err != nil {
  1144. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  1145. }
  1146. hosts := strings.Split(strings.Trim(req.Target, ","), ",")
  1147. err = s.RemoveHostFromInv(YosaiServerInventory, hosts...)
  1148. if err != nil {
  1149. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  1150. }
  1151. return *daemon.NewSockMessage(daemon.MsgRequest, daemon.REQUEST_OK, []byte(fmt.Sprintf("Host: %v removed from the inventory", hosts)))
  1152. }
  1153. /*
  1154. Wrapping the add host function in a route friendly interface
  1155. :param msg: a message to parse that was recieved from the daemon socket
  1156. */
  1157. func (s SemaphoreConnection) AddHostHandler(msg daemon.SockMessage) daemon.SockMessage {
  1158. var req SemaphoreRequest
  1159. err := json.Unmarshal(msg.Body, &req)
  1160. if err != nil {
  1161. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  1162. }
  1163. hosts := strings.Split(strings.Trim(req.Target, ","), ",")
  1164. vpnHosts := []daemon.VpnServer{}
  1165. for i := range hosts {
  1166. server, err := s.Config.GetServer(hosts[i])
  1167. if err != nil {
  1168. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  1169. }
  1170. vpnHosts = append(vpnHosts, server)
  1171. }
  1172. err = s.AddHostToInv(YosaiServerInventory, vpnHosts...)
  1173. if err != nil {
  1174. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  1175. }
  1176. return *daemon.NewSockMessage(daemon.MsgRequest, daemon.REQUEST_OK, []byte(fmt.Sprintf("Host: %v added to the inventory", hosts)))
  1177. }
  1178. type SemaphoreRouter struct {
  1179. routes map[daemon.Method]func(daemon.SockMessage) daemon.SockMessage
  1180. }
  1181. func (s *SemaphoreRouter) Register(method daemon.Method, callable func(daemon.SockMessage) daemon.SockMessage) {
  1182. s.routes[method] = callable
  1183. }
  1184. func (s *SemaphoreRouter) Routes() map[daemon.Method]func(daemon.SockMessage) daemon.SockMessage {
  1185. return s.routes
  1186. }
  1187. func NewSemaphoreRouter() *SemaphoreRouter {
  1188. return &SemaphoreRouter{routes: map[daemon.Method]func(daemon.SockMessage) daemon.SockMessage{}}
  1189. }
  1190. /*
  1191. ######################################################
  1192. ############# YAML INVENTORY STRUCTS #################
  1193. ######################################################
  1194. */
  1195. type YamlInventory struct {
  1196. All yamlInvAll `yaml:"all"`
  1197. }
  1198. type yamlInvAll struct {
  1199. Hosts map[string]yamlVars `yaml:"hosts"`
  1200. }
  1201. type yamlVars struct {
  1202. AnsibleSshCommonArgs string `yaml:"ansible_ssh_common_args"`
  1203. MachineType string `yaml:"machine_type"`
  1204. MachineSubType string `yaml:"machine_subtype"`
  1205. VpnNetworkAddress string `yaml:"vpn_network_address"`
  1206. VpnServerPort int `yaml:"vpn_server_port"`
  1207. Clients map[string]yamlVpnClient `yaml:"clients"`
  1208. SecretsProvider string `yaml:"secrets_provider"`
  1209. VpnNetMask int `yaml:"vpn_netmask"`
  1210. Name string `yaml:"name"`
  1211. }
  1212. type yamlVpnClient struct {
  1213. Name string `yaml:"name"`
  1214. Ipv4 string `yaml:"ipv4"`
  1215. Pubkey string `yaml:"pubkey"`
  1216. }
  1217. /*
  1218. YAML inventory builder function
  1219. :param hosts: a list of host IP addresses to add to the VPN server inventory
  1220. */
  1221. func (s SemaphoreConnection) YamlInventoryBuilder(hosts []daemon.VpnServer) YamlInventory {
  1222. hostmap := map[string]yamlVars{}
  1223. clientmap := map[string]yamlVpnClient{}
  1224. clients := s.Config.VpnClients()
  1225. for i := range clients {
  1226. client := clients[i]
  1227. clientmap[client.Name] = yamlVpnClient{Name: client.Name, Ipv4: client.VpnIpv4.String(), Pubkey: client.Pubkey}
  1228. }
  1229. for i := range hosts {
  1230. server := hosts[i]
  1231. hostmap[hosts[i].WanIpv4] = yamlVars{
  1232. AnsibleSshCommonArgs: "-o StrictHostKeyChecking=no",
  1233. MachineType: "vpn",
  1234. MachineSubType: "server",
  1235. VpnNetworkAddress: server.VpnIpv4.String(),
  1236. VpnServerPort: server.Port,
  1237. Clients: clientmap,
  1238. SecretsProvider: s.Config.SecretsBackend(),
  1239. VpnNetMask: s.Config.Service.VpnMask,
  1240. Name: server.Name}
  1241. }
  1242. return YamlInventory{
  1243. All: yamlInvAll{
  1244. Hosts: hostmap,
  1245. },
  1246. }
  1247. }
  1248. /*
  1249. ##########################################
  1250. ################ ERRORS ##################
  1251. ##########################################
  1252. */
  1253. type SemaphoreClientError struct {
  1254. Msg string
  1255. }
  1256. // Implementing error interface
  1257. func (s *SemaphoreClientError) Error() string {
  1258. return fmt.Sprintf("There was an error with the call to the semaphore server: '%s'", s.Msg)
  1259. }
  1260. type KeyNotFound struct{ Keyname string }
  1261. func (k *KeyNotFound) Error() string {
  1262. return fmt.Sprintf("Key '%s' was not found in the Semaphore Keystore", k.Keyname)
  1263. }
  1264. type SemaphoreTimeout struct {
  1265. Tries int
  1266. }
  1267. func (s *SemaphoreTimeout) Error() string {
  1268. return "Semaphore job execution poll timed out after: " + fmt.Sprint(s.Tries) + " calls to the server."
  1269. }