client.go 14 KB

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