passworddepot_api.go 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. /*
  2. Licensed under the Apache License, Version 2.0 (the "License");
  3. you may not use this file except in compliance with the License.
  4. You may obtain a copy of the License at
  5. http://www.apache.org/licenses/LICENSE-2.0
  6. Unless required by applicable law or agreed to in writing, software
  7. distributed under the License is distributed on an "AS IS" BASIS,
  8. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9. See the License for the specific language governing permissions and
  10. limitations under the License.
  11. */
  12. package passworddepot
  13. import (
  14. "bytes"
  15. "context"
  16. "crypto/tls"
  17. "encoding/json"
  18. "errors"
  19. "fmt"
  20. "net/http"
  21. "strings"
  22. "time"
  23. )
  24. const (
  25. DoRequestError = "error: do request: %w"
  26. )
  27. type HTTPClient interface {
  28. Do(*http.Request) (*http.Response, error)
  29. }
  30. type AccessData struct {
  31. ClientID string `json:"client_id"`
  32. AccessToken string `json:"access_token"`
  33. }
  34. type Databases struct {
  35. Databases []struct {
  36. Name string `json:"name"`
  37. Fingerprint string `json:"fingerprint"`
  38. Date time.Time `json:"date"`
  39. Rights string `json:"rights"`
  40. Reasondelete string `json:"reasondelete"`
  41. } `json:"databases"`
  42. Infoclasses string `json:"infoclasses"`
  43. Policyforce string `json:"policyforce"`
  44. Policyminlength string `json:"policyminlength"`
  45. Policyincludeatleast string `json:"policyincludeatleast"`
  46. Policymingroups string `json:"policymingroups"`
  47. Policyselectedgroups string `json:"policyselectedgroups"`
  48. }
  49. type DatabaseEntries struct {
  50. Name string `json:"name"`
  51. Parent string `json:"parent"`
  52. Entries []Entry `json:"entries"`
  53. Infoclasses string `json:"infoclasses"`
  54. Reasondelete string `json:"reasondelete"`
  55. }
  56. type Entry struct {
  57. Name string `json:"name"`
  58. Login string `json:"login"`
  59. Password string `json:"pass"`
  60. URL string `json:"url"`
  61. Importance string `json:"importance"`
  62. Date time.Time `json:"date"`
  63. Icon string `json:"icon"`
  64. Secondeye string `json:"secondeye"`
  65. Fingerprint string `json:"fingerprint"`
  66. Rights string `json:"rights"`
  67. Itemclass string `json:"itemclass"`
  68. }
  69. type API struct {
  70. client HTTPClient
  71. baseURL string
  72. hostPort string
  73. secret *AccessData
  74. password string
  75. username string
  76. }
  77. type SecretEntry struct {
  78. Name string `json:"name"`
  79. Fingerprint string `json:"fingerprint"`
  80. Itemclass string `json:"itemclass"`
  81. Login string `json:"login"`
  82. Pass string `json:"pass"`
  83. URL string `json:"url"`
  84. Importance string `json:"importance"`
  85. Date time.Time `json:"date"`
  86. Comment string `json:"comment"`
  87. Expirydate string `json:"expirydate"`
  88. Tags string `json:"tags"`
  89. Author string `json:"author"`
  90. Category string `json:"category"`
  91. Icon string `json:"icon"`
  92. Secondeye string `json:"secondeye"`
  93. Secondpass string `json:"secondpass"`
  94. Template string `json:"template"`
  95. Acm string `json:"acm"`
  96. Paramstr string `json:"paramstr"`
  97. Loginid string `json:"loginid"`
  98. Passid string `json:"passid"`
  99. Donotaddon string `json:"donotaddon"`
  100. Markassafe string `json:"markassafe"`
  101. Safemode string `json:"safemode"`
  102. }
  103. var errDBNotFound = errors.New("database not found")
  104. var errSecretNotFound = errors.New("secret not found")
  105. // load tls certificates
  106. func NewAPI(ctx context.Context, baseURL, username, password, hostPort string) (*API, error) {
  107. api := &API{
  108. baseURL: baseURL,
  109. hostPort: hostPort,
  110. username: username,
  111. password: password,
  112. }
  113. tr := &http.Transport{
  114. TLSClientConfig: &tls.Config{MinVersion: tls.VersionTLS12},
  115. }
  116. api.client = &http.Client{Transport: tr}
  117. err := api.login(ctx)
  118. if err != nil {
  119. return nil, fmt.Errorf("failed to login: %w", err)
  120. }
  121. return api, nil
  122. }
  123. func (api *API) doAuthenticatedRequest(r *http.Request) (*http.Response, error) {
  124. r.Header.Add("access_token", api.secret.AccessToken)
  125. r.Header.Add("client_id", api.secret.ClientID)
  126. return api.client.Do(r)
  127. }
  128. func (api *API) getDatabaseFingerprint(database string) (string, error) {
  129. databases, err := api.ListDatabases()
  130. if err != nil {
  131. return "", fmt.Errorf("error: getting database list: %w", err)
  132. }
  133. for _, db := range databases.Databases {
  134. if strings.Contains(db.Name, database) {
  135. return db.Fingerprint, nil
  136. }
  137. }
  138. return "", errDBNotFound
  139. }
  140. func (api *API) getSecretFingerprint(databaseFingerprint, secretName, folder string) (string, error) {
  141. secrets, err := api.ListSecrets(databaseFingerprint, folder)
  142. if err != nil {
  143. return "", fmt.Errorf("error: getting secrets list: %w", err)
  144. }
  145. parts := strings.Split(secretName, ".")
  146. searchName := parts[0]
  147. var fingerprint string
  148. for _, entry := range secrets.Entries {
  149. if strings.Contains(entry.Name, searchName) {
  150. fingerprint = entry.Fingerprint
  151. if len(parts) > 1 {
  152. return api.getSecretFingerprint(databaseFingerprint, strings.Join(parts[1:], "."), fingerprint)
  153. }
  154. return fingerprint, nil
  155. }
  156. }
  157. return "", errSecretNotFound
  158. }
  159. func (api *API) getendpointURL(endpoint string) string {
  160. return fmt.Sprintf("https://%s:%s/v1.0/%s", api.baseURL, api.hostPort, endpoint)
  161. }
  162. func (api *API) login(ctx context.Context) error {
  163. loginRequest, err := http.NewRequestWithContext(ctx, "GET", api.getendpointURL("login"), http.NoBody)
  164. if err != nil {
  165. return fmt.Errorf("error creating request: %w", err)
  166. }
  167. loginRequest.Header.Add("user", api.username)
  168. loginRequest.Header.Add("pass", api.password)
  169. resp, err := api.client.Do(loginRequest) //nolint:bodyclose // linters bug
  170. if err != nil {
  171. return fmt.Errorf(DoRequestError, err)
  172. }
  173. accessData := AccessData{}
  174. err = ReadAndUnmarshal(resp, &accessData)
  175. if err != nil {
  176. return fmt.Errorf("error: failed to unmarshal response body: %w", err)
  177. }
  178. api.secret = &accessData
  179. return nil
  180. }
  181. func (api *API) ListSecrets(dbFingerprint, folder string) (DatabaseEntries, error) {
  182. endpointURL := api.getendpointURL(fmt.Sprintf("list?db=%s", dbFingerprint))
  183. if folder != "" {
  184. endpointURL = fmt.Sprintf("%s&folder=%s", endpointURL, folder)
  185. }
  186. listSecrets, err := http.NewRequest("GET", endpointURL, http.NoBody)
  187. if err != nil {
  188. return DatabaseEntries{}, fmt.Errorf("error: creating secrets request: %w", err)
  189. }
  190. respSecretsList, err := api.doAuthenticatedRequest(listSecrets) //nolint:bodyclose // linters bug
  191. if err != nil {
  192. return DatabaseEntries{}, fmt.Errorf(DoRequestError, err)
  193. }
  194. dbEntries := DatabaseEntries{}
  195. err = ReadAndUnmarshal(respSecretsList, &dbEntries)
  196. return dbEntries, err
  197. }
  198. func ReadAndUnmarshal(resp *http.Response, target any) error {
  199. var buf bytes.Buffer
  200. defer func() {
  201. _ = resp.Body.Close()
  202. }()
  203. if resp.StatusCode < 200 || resp.StatusCode > 299 {
  204. return fmt.Errorf("failed to authenticate with the given credentials: %d %s", resp.StatusCode, buf.String())
  205. }
  206. _, err := buf.ReadFrom(resp.Body)
  207. if err != nil {
  208. return err
  209. }
  210. return json.Unmarshal(buf.Bytes(), target)
  211. }
  212. func (api *API) ListDatabases() (Databases, error) {
  213. listDBRequest, err := http.NewRequest("GET", api.getendpointURL("list"), http.NoBody)
  214. if err != nil {
  215. return Databases{}, fmt.Errorf("error: creating db request: %w", err)
  216. }
  217. respDBList, err := api.doAuthenticatedRequest(listDBRequest) //nolint:bodyclose // linters bug
  218. if err != nil {
  219. return Databases{}, fmt.Errorf(DoRequestError, err)
  220. }
  221. databases := Databases{}
  222. err = ReadAndUnmarshal(respDBList, &databases)
  223. return databases, err
  224. }
  225. func (api *API) GetSecret(database, secretName string) (SecretEntry, error) {
  226. dbFingerprint, err := api.getDatabaseFingerprint(database)
  227. if err != nil {
  228. return SecretEntry{}, fmt.Errorf("error: getting DB fingerprint: %w", err)
  229. }
  230. secretFingerprint, err := api.getSecretFingerprint(dbFingerprint, secretName, "")
  231. if err != nil {
  232. return SecretEntry{}, fmt.Errorf("error: getting Secret fingerprint: %w", err)
  233. }
  234. readSecretRequest, err := http.NewRequest("GET", api.getendpointURL(fmt.Sprintf("read?db=%s&entry=%s", dbFingerprint, secretFingerprint)), http.NoBody)
  235. if err != nil {
  236. return SecretEntry{}, fmt.Errorf("error: creating secrets request: %w", err)
  237. }
  238. respSecretRead, err := api.doAuthenticatedRequest(readSecretRequest) //nolint:bodyclose // linters bug
  239. if err != nil {
  240. return SecretEntry{}, fmt.Errorf(DoRequestError, err)
  241. }
  242. secretEntry := SecretEntry{}
  243. err = ReadAndUnmarshal(respSecretRead, &secretEntry)
  244. return secretEntry, err
  245. }
  246. func (s SecretEntry) ToMap() map[string][]byte {
  247. m := make(map[string][]byte)
  248. m["name"] = []byte(s.Name)
  249. m["fingerprint"] = []byte(s.Fingerprint)
  250. m["itemclass"] = []byte(s.Itemclass)
  251. m["login"] = []byte(s.Login)
  252. m["pass"] = []byte(s.Pass)
  253. m["url"] = []byte(s.URL)
  254. m["importance"] = []byte(s.Importance)
  255. m["comment"] = []byte(s.Comment)
  256. m["expirydate"] = []byte(s.Expirydate)
  257. m["tags"] = []byte(s.Tags)
  258. m["author"] = []byte(s.Author)
  259. m["category"] = []byte(s.Category)
  260. m["icon"] = []byte(s.Icon)
  261. m["secondeye"] = []byte(s.Secondeye)
  262. m["secondpass"] = []byte(s.Secondpass)
  263. m["template"] = []byte(s.Template)
  264. m["acm"] = []byte(s.Acm)
  265. m["paramstr"] = []byte(s.Paramstr)
  266. m["loginid"] = []byte(s.Loginid)
  267. m["passid"] = []byte(s.Passid)
  268. m["donotaddon"] = []byte(s.Donotaddon)
  269. m["markassafe"] = []byte(s.Markassafe)
  270. m["safemode"] = []byte(s.Safemode)
  271. return m
  272. }