client.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  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 keepersecurity
  13. import (
  14. "context"
  15. "encoding/json"
  16. "errors"
  17. "fmt"
  18. "maps"
  19. "regexp"
  20. "strings"
  21. ksm "github.com/keeper-security/secrets-manager-go/core"
  22. corev1 "k8s.io/api/core/v1"
  23. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  24. )
  25. const (
  26. errKeeperSecuritySecretsNotFound = "unable to find secrets. %w"
  27. errKeeperSecuritySecretNotFound = "unable to find secret %s. Error: %w"
  28. errKeeperSecuritySecretNotUnique = "more than 1 secret %s found"
  29. errKeeperSecurityNoSecretsFound = "no secrets found"
  30. errKeeperSecurityInvalidSecretInvalidFormat = "invalid secret. Invalid format: %w"
  31. errKeeperSecurityInvalidSecretDuplicatedKey = "invalid Secret. Following keys are duplicated %s"
  32. errKeeperSecurityInvalidProperty = "invalid Property. Secret %s does not have any key matching %s"
  33. errKeeperSecurityInvalidField = "invalid Field. Key %s does not exists"
  34. errKeeperSecurityNoFields = "invalid Secret. Secret %s does not contain any valid field/file"
  35. keeperSecurityFileRef = "fileRef"
  36. keeperSecurityMfa = "oneTimeCode"
  37. errTagsNotImplemented = "'find.tags' is not implemented in the KeeperSecurity provider"
  38. errPathNotImplemented = "'find.path' is not implemented in the KeeperSecurity provider"
  39. errInvalidJSONSecret = "invalid Secret. Secret %s can not be converted to JSON. %w"
  40. errInvalidRegex = "find.name.regex. Invalid Regular expresion %s. %w"
  41. errInvalidRemoteRefKey = "match.remoteRef.remoteKey. Invalid format. Format should match secretName/key got %s"
  42. errInvalidSecretType = "ESO can only push/delete records of type %s. Secret %s is type %s"
  43. errFieldNotFound = "secret %s does not contain any custom field with label %s"
  44. externalSecretType = "externalSecrets"
  45. secretType = "secret"
  46. LoginType = "login"
  47. LoginTypeExpr = "login|username"
  48. PasswordType = "password"
  49. URLTypeExpr = "url|baseurl"
  50. URLType = "url"
  51. )
  52. type Client struct {
  53. ksmClient SecurityClient
  54. folderID string
  55. }
  56. type SecurityClient interface {
  57. GetSecrets(filter []string) ([]*ksm.Record, error)
  58. GetSecretByTitle(recordTitle string) (*ksm.Record, error)
  59. GetSecretsByTitle(recordTitle string) (records []*ksm.Record, err error)
  60. CreateSecretWithRecordData(recUID, folderUID string, recordData *ksm.RecordCreate) (string, error)
  61. DeleteSecrets(recrecordUids []string) (map[string]string, error)
  62. Save(record *ksm.Record) error
  63. }
  64. type Field struct {
  65. Type string `json:"type"`
  66. Value []any `json:"value"`
  67. }
  68. type CustomField struct {
  69. Type string `json:"type"`
  70. Label string `json:"label"`
  71. Value []any `json:"value"`
  72. }
  73. type File struct {
  74. Title string `json:"type"`
  75. Content string `json:"content"`
  76. }
  77. type Secret struct {
  78. Title string `json:"title"`
  79. Type string `json:"type"`
  80. Fields []Field `json:"fields"`
  81. Custom []CustomField `json:"custom"`
  82. Files []File `json:"files"`
  83. }
  84. func (c *Client) Validate() (esv1.ValidationResult, error) {
  85. return esv1.ValidationResultReady, nil
  86. }
  87. func (c *Client) GetSecret(_ context.Context, ref esv1.ExternalSecretDataRemoteRef) ([]byte, error) {
  88. record, err := c.findSecretByID(ref.Key)
  89. if err != nil {
  90. return nil, err
  91. }
  92. secret, err := c.getValidKeeperSecret(record)
  93. if err != nil {
  94. return nil, err
  95. }
  96. return secret.getItem(ref)
  97. }
  98. func (c *Client) GetSecretMap(_ context.Context, ref esv1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  99. record, err := c.findSecretByID(ref.Key)
  100. if err != nil {
  101. return nil, err
  102. }
  103. secret, err := c.getValidKeeperSecret(record)
  104. if err != nil {
  105. return nil, err
  106. }
  107. return secret.getItems(ref)
  108. }
  109. func (c *Client) GetAllSecrets(_ context.Context, ref esv1.ExternalSecretFind) (map[string][]byte, error) {
  110. if ref.Tags != nil {
  111. return nil, errors.New(errTagsNotImplemented)
  112. }
  113. if ref.Path != nil {
  114. return nil, errors.New(errPathNotImplemented)
  115. }
  116. secretData := make(map[string][]byte)
  117. records, err := c.findSecrets()
  118. if err != nil {
  119. return nil, err
  120. }
  121. for _, record := range records {
  122. secret, err := c.getValidKeeperSecret(record)
  123. if err != nil {
  124. return nil, err
  125. }
  126. match, err := regexp.MatchString(ref.Name.RegExp, secret.Title)
  127. if err != nil {
  128. return nil, fmt.Errorf(errInvalidRegex, ref.Name.RegExp, err)
  129. }
  130. if !match {
  131. continue
  132. }
  133. secretData[secret.Title], err = secret.getItem(esv1.ExternalSecretDataRemoteRef{})
  134. if err != nil {
  135. return nil, err
  136. }
  137. }
  138. return secretData, nil
  139. }
  140. func (c *Client) Close(_ context.Context) error {
  141. return nil
  142. }
  143. func (c *Client) PushSecret(_ context.Context, secret *corev1.Secret, data esv1.PushSecretData) error {
  144. if data.GetSecretKey() == "" {
  145. return errors.New("pushing the whole secret is not yet implemented")
  146. }
  147. value := secret.Data[data.GetSecretKey()]
  148. parts, err := c.buildSecretNameAndKey(data)
  149. if err != nil {
  150. return err
  151. }
  152. record, err := c.findSecretByName(parts[0])
  153. if err != nil {
  154. return err
  155. }
  156. if record != nil {
  157. if record.Type() == externalSecretType {
  158. return c.updateSecret(record, parts[1], value)
  159. } else {
  160. return fmt.Errorf(errInvalidSecretType, externalSecretType, record.Title(), record.Type())
  161. }
  162. } else {
  163. _, err = c.createSecret(parts[0], parts[1], value)
  164. return err
  165. }
  166. }
  167. func (c *Client) DeleteSecret(_ context.Context, remoteRef esv1.PushSecretRemoteRef) error {
  168. parts, err := c.buildSecretNameAndKey(remoteRef)
  169. if err != nil {
  170. return err
  171. }
  172. secret, err := c.findSecretByName(parts[0])
  173. if err != nil {
  174. return err
  175. } else if secret == nil {
  176. return nil // not found == already deleted (success)
  177. }
  178. if secret.Type() != externalSecretType {
  179. return fmt.Errorf(errInvalidSecretType, externalSecretType, secret.Title(), secret.Type())
  180. }
  181. _, err = c.ksmClient.DeleteSecrets([]string{secret.Uid})
  182. return err
  183. }
  184. func (c *Client) SecretExists(_ context.Context, _ esv1.PushSecretRemoteRef) (bool, error) {
  185. return false, errors.New("not implemented")
  186. }
  187. func (c *Client) buildSecretNameAndKey(remoteRef esv1.PushSecretRemoteRef) ([]string, error) {
  188. parts := strings.Split(remoteRef.GetRemoteKey(), "/")
  189. if len(parts) != 2 {
  190. return nil, fmt.Errorf(errInvalidRemoteRefKey, remoteRef.GetRemoteKey())
  191. }
  192. return parts, nil
  193. }
  194. func (c *Client) createSecret(name, key string, value []byte) (string, error) {
  195. normalizedKey := strings.ToLower(key)
  196. externalSecretRecord := ksm.NewRecordCreate(externalSecretType, name)
  197. login := regexp.MustCompile(LoginTypeExpr)
  198. pass := regexp.MustCompile(PasswordType)
  199. url := regexp.MustCompile(URLTypeExpr)
  200. switch {
  201. case login.MatchString(normalizedKey):
  202. externalSecretRecord.Fields = append(externalSecretRecord.Fields,
  203. ksm.NewLogin(string(value)),
  204. )
  205. case pass.MatchString(normalizedKey):
  206. externalSecretRecord.Fields = append(externalSecretRecord.Fields,
  207. ksm.NewPassword(string(value)),
  208. )
  209. case url.MatchString(normalizedKey):
  210. externalSecretRecord.Fields = append(externalSecretRecord.Fields,
  211. ksm.NewUrl(string(value)),
  212. )
  213. default:
  214. field := ksm.KeeperRecordField{Type: secretType, Label: key}
  215. externalSecretRecord.Custom = append(externalSecretRecord.Custom,
  216. ksm.Secret{KeeperRecordField: field, Value: []string{string(value)}},
  217. )
  218. }
  219. return c.ksmClient.CreateSecretWithRecordData("", c.folderID, externalSecretRecord)
  220. }
  221. func (c *Client) updateSecret(secret *ksm.Record, key string, value []byte) error {
  222. normalizedKey := strings.ToLower(key)
  223. login := regexp.MustCompile(LoginTypeExpr)
  224. pass := regexp.MustCompile(PasswordType)
  225. url := regexp.MustCompile(URLTypeExpr)
  226. custom := false
  227. switch {
  228. case login.MatchString(normalizedKey):
  229. secret.SetFieldValueSingle(LoginType, string(value))
  230. case pass.MatchString(normalizedKey):
  231. secret.SetPassword(string(value))
  232. case url.MatchString(normalizedKey):
  233. secret.SetFieldValueSingle(URLType, string(value))
  234. default:
  235. custom = true
  236. }
  237. if custom {
  238. field := secret.GetCustomFieldValueByLabel(key)
  239. if field != "" {
  240. secret.SetCustomFieldValueSingle(key, string(value))
  241. } else {
  242. return fmt.Errorf(errFieldNotFound, secret.Title(), key)
  243. }
  244. }
  245. return c.ksmClient.Save(secret)
  246. }
  247. func (c *Client) getValidKeeperSecret(secret *ksm.Record) (*Secret, error) {
  248. keeperSecret := Secret{}
  249. err := json.Unmarshal([]byte(secret.RawJson), &keeperSecret)
  250. if err != nil {
  251. return nil, fmt.Errorf(errKeeperSecurityInvalidSecretInvalidFormat, err)
  252. }
  253. keeperSecret.addFiles(secret.Files)
  254. err = keeperSecret.validate()
  255. if err != nil {
  256. return nil, err
  257. }
  258. return &keeperSecret, nil
  259. }
  260. func (c *Client) findSecrets() ([]*ksm.Record, error) {
  261. records, err := c.ksmClient.GetSecrets([]string{})
  262. if err != nil {
  263. return nil, fmt.Errorf(errKeeperSecuritySecretsNotFound, err)
  264. }
  265. return records, nil
  266. }
  267. func (c *Client) findSecretByID(id string) (*ksm.Record, error) {
  268. records, err := c.ksmClient.GetSecrets([]string{id})
  269. if err != nil {
  270. return nil, fmt.Errorf(errKeeperSecuritySecretNotFound, id, err)
  271. }
  272. if len(records) == 0 {
  273. return nil, errors.New(errKeeperSecurityNoSecretsFound)
  274. }
  275. if len(records) > 1 {
  276. return nil, fmt.Errorf(errKeeperSecuritySecretNotUnique, id)
  277. }
  278. return records[0], nil
  279. }
  280. func (c *Client) findSecretByName(name string) (*ksm.Record, error) {
  281. records, err := c.ksmClient.GetSecretsByTitle(name)
  282. if err != nil {
  283. return nil, err
  284. }
  285. // filter in-place, preserve only records of type externalSecretType
  286. n := 0
  287. for _, record := range records {
  288. if record.Type() == externalSecretType {
  289. records[n] = record
  290. n++
  291. }
  292. }
  293. records = records[:n]
  294. // record not found is not an error - handled differently:
  295. // PushSecret will create new record instead
  296. // DeleteSecret will consider record already deleted (no error)
  297. if len(records) == 0 {
  298. return nil, nil
  299. } else if len(records) == 1 {
  300. return records[0], nil
  301. }
  302. // len(records) > 1
  303. return nil, fmt.Errorf(errKeeperSecuritySecretNotUnique, name)
  304. }
  305. func (s *Secret) validate() error {
  306. fields := make(map[string]int)
  307. for _, field := range s.Fields {
  308. fields[field.Type]++
  309. }
  310. for _, customField := range s.Custom {
  311. fields[customField.Label]++
  312. }
  313. for _, file := range s.Files {
  314. fields[file.Title]++
  315. }
  316. var duplicates []string
  317. for key, ocurrences := range fields {
  318. if ocurrences > 1 {
  319. duplicates = append(duplicates, key)
  320. }
  321. }
  322. if len(duplicates) != 0 {
  323. return fmt.Errorf(errKeeperSecurityInvalidSecretDuplicatedKey, strings.Join(duplicates, ", "))
  324. }
  325. return nil
  326. }
  327. func (s *Secret) addFiles(keeperFiles []*ksm.KeeperFile) {
  328. for _, f := range keeperFiles {
  329. s.Files = append(
  330. s.Files,
  331. File{
  332. Title: f.Title,
  333. Content: string(f.GetFileData()),
  334. },
  335. )
  336. }
  337. }
  338. func (s *Secret) getItem(ref esv1.ExternalSecretDataRemoteRef) ([]byte, error) {
  339. if ref.Property != "" {
  340. return s.getProperty(ref.Property)
  341. }
  342. secret, err := s.toString()
  343. return []byte(secret), err
  344. }
  345. func (s *Secret) getItems(ref esv1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  346. secretData := make(map[string][]byte)
  347. if ref.Property != "" {
  348. value, err := s.getProperty(ref.Property)
  349. if err != nil {
  350. return nil, err
  351. }
  352. secretData[ref.Property] = value
  353. return secretData, nil
  354. }
  355. fields := s.getFields()
  356. maps.Copy(secretData, fields)
  357. customFields := s.getCustomFields()
  358. maps.Copy(secretData, customFields)
  359. files := s.getFiles()
  360. maps.Copy(secretData, files)
  361. if len(secretData) == 0 {
  362. return nil, fmt.Errorf(errKeeperSecurityNoFields, s.Title)
  363. }
  364. return secretData, nil
  365. }
  366. func getFieldValue(value []any) []byte {
  367. if len(value) < 1 {
  368. return []byte{}
  369. } else if len(value) == 1 {
  370. res, _ := json.Marshal(value[0])
  371. if str, ok := value[0].(string); ok {
  372. res = []byte(str)
  373. }
  374. return res
  375. } else {
  376. res, _ := json.Marshal(value)
  377. return res
  378. }
  379. }
  380. func (s *Secret) getField(key string) ([]byte, error) {
  381. for _, field := range s.Fields {
  382. if field.Type == key && field.Type != keeperSecurityFileRef && field.Type != keeperSecurityMfa && len(field.Value) > 0 {
  383. return getFieldValue(field.Value), nil
  384. }
  385. }
  386. return nil, fmt.Errorf(errKeeperSecurityInvalidField, key)
  387. }
  388. func (s *Secret) getFields() map[string][]byte {
  389. secretData := make(map[string][]byte)
  390. for _, field := range s.Fields {
  391. if len(field.Value) > 0 {
  392. secretData[field.Type] = getFieldValue(field.Value)
  393. }
  394. }
  395. return secretData
  396. }
  397. func (s *Secret) getCustomField(key string) ([]byte, error) {
  398. for _, field := range s.Custom {
  399. if field.Label == key && len(field.Value) > 0 {
  400. return getFieldValue(field.Value), nil
  401. }
  402. }
  403. return nil, fmt.Errorf(errKeeperSecurityInvalidField, key)
  404. }
  405. func (s *Secret) getCustomFields() map[string][]byte {
  406. secretData := make(map[string][]byte)
  407. for _, field := range s.Custom {
  408. if len(field.Value) > 0 {
  409. secretData[field.Label] = getFieldValue(field.Value)
  410. }
  411. }
  412. return secretData
  413. }
  414. func (s *Secret) getFile(key string) ([]byte, error) {
  415. for _, file := range s.Files {
  416. if file.Title == key {
  417. return []byte(file.Content), nil
  418. }
  419. }
  420. return nil, fmt.Errorf(errKeeperSecurityInvalidField, key)
  421. }
  422. func (s *Secret) getProperty(key string) ([]byte, error) {
  423. field, _ := s.getField(key)
  424. if field != nil {
  425. return field, nil
  426. }
  427. customField, _ := s.getCustomField(key)
  428. if customField != nil {
  429. return customField, nil
  430. }
  431. file, _ := s.getFile(key)
  432. if file != nil {
  433. return file, nil
  434. }
  435. return nil, fmt.Errorf(errKeeperSecurityInvalidProperty, s.Title, key)
  436. }
  437. func (s *Secret) getFiles() map[string][]byte {
  438. secretData := make(map[string][]byte)
  439. for _, file := range s.Files {
  440. secretData[file.Title] = []byte(file.Content)
  441. }
  442. return secretData
  443. }
  444. func (s *Secret) toString() (string, error) {
  445. secretJSON, err := json.Marshal(s)
  446. if err != nil {
  447. return "", fmt.Errorf(errInvalidJSONSecret, s.Title, err)
  448. }
  449. return string(secretJSON), nil
  450. }