client.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  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. return records[0], nil
  276. }
  277. func (c *Client) findSecretByName(name string) (*ksm.Record, error) {
  278. records, err := c.ksmClient.GetSecretsByTitle(name)
  279. if err != nil {
  280. return nil, err
  281. }
  282. // filter in-place, preserve only records of type externalSecretType
  283. n := 0
  284. for _, record := range records {
  285. if record.Type() == externalSecretType {
  286. records[n] = record
  287. n++
  288. }
  289. }
  290. records = records[:n]
  291. // record not found is not an error - handled differently:
  292. // PushSecret will create new record instead
  293. // DeleteSecret will consider record already deleted (no error)
  294. if len(records) == 0 {
  295. return nil, nil
  296. } else if len(records) == 1 {
  297. return records[0], nil
  298. }
  299. // len(records) > 1
  300. return nil, fmt.Errorf(errKeeperSecuritySecretNotUnique, name)
  301. }
  302. func (s *Secret) validate() error {
  303. fields := make(map[string]int)
  304. for _, field := range s.Fields {
  305. fields[field.Type]++
  306. }
  307. for _, customField := range s.Custom {
  308. fields[customField.Label]++
  309. }
  310. for _, file := range s.Files {
  311. fields[file.Title]++
  312. }
  313. var duplicates []string
  314. for key, ocurrences := range fields {
  315. if ocurrences > 1 {
  316. duplicates = append(duplicates, key)
  317. }
  318. }
  319. if len(duplicates) != 0 {
  320. return fmt.Errorf(errKeeperSecurityInvalidSecretDuplicatedKey, strings.Join(duplicates, ", "))
  321. }
  322. return nil
  323. }
  324. func (s *Secret) addFiles(keeperFiles []*ksm.KeeperFile) {
  325. for _, f := range keeperFiles {
  326. s.Files = append(
  327. s.Files,
  328. File{
  329. Title: f.Title,
  330. Content: string(f.GetFileData()),
  331. },
  332. )
  333. }
  334. }
  335. func (s *Secret) getItem(ref esv1.ExternalSecretDataRemoteRef) ([]byte, error) {
  336. if ref.Property != "" {
  337. return s.getProperty(ref.Property)
  338. }
  339. secret, err := s.toString()
  340. return []byte(secret), err
  341. }
  342. func (s *Secret) getItems(ref esv1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  343. secretData := make(map[string][]byte)
  344. if ref.Property != "" {
  345. value, err := s.getProperty(ref.Property)
  346. if err != nil {
  347. return nil, err
  348. }
  349. secretData[ref.Property] = value
  350. return secretData, nil
  351. }
  352. fields := s.getFields()
  353. maps.Copy(secretData, fields)
  354. customFields := s.getCustomFields()
  355. maps.Copy(secretData, customFields)
  356. files := s.getFiles()
  357. maps.Copy(secretData, files)
  358. if len(secretData) == 0 {
  359. return nil, fmt.Errorf(errKeeperSecurityNoFields, s.Title)
  360. }
  361. return secretData, nil
  362. }
  363. func getFieldValue(value []any) []byte {
  364. if len(value) < 1 {
  365. return []byte{}
  366. } else if len(value) == 1 {
  367. res, _ := json.Marshal(value[0])
  368. if str, ok := value[0].(string); ok {
  369. res = []byte(str)
  370. }
  371. return res
  372. } else {
  373. res, _ := json.Marshal(value)
  374. return res
  375. }
  376. }
  377. func (s *Secret) getField(key string) ([]byte, error) {
  378. for _, field := range s.Fields {
  379. if field.Type == key && field.Type != keeperSecurityFileRef && field.Type != keeperSecurityMfa && len(field.Value) > 0 {
  380. return getFieldValue(field.Value), nil
  381. }
  382. }
  383. return nil, fmt.Errorf(errKeeperSecurityInvalidField, key)
  384. }
  385. func (s *Secret) getFields() map[string][]byte {
  386. secretData := make(map[string][]byte)
  387. for _, field := range s.Fields {
  388. if len(field.Value) > 0 {
  389. secretData[field.Type] = getFieldValue(field.Value)
  390. }
  391. }
  392. return secretData
  393. }
  394. func (s *Secret) getCustomField(key string) ([]byte, error) {
  395. for _, field := range s.Custom {
  396. if field.Label == key && len(field.Value) > 0 {
  397. return getFieldValue(field.Value), nil
  398. }
  399. }
  400. return nil, fmt.Errorf(errKeeperSecurityInvalidField, key)
  401. }
  402. func (s *Secret) getCustomFields() map[string][]byte {
  403. secretData := make(map[string][]byte)
  404. for _, field := range s.Custom {
  405. if len(field.Value) > 0 {
  406. secretData[field.Label] = getFieldValue(field.Value)
  407. }
  408. }
  409. return secretData
  410. }
  411. func (s *Secret) getFile(key string) ([]byte, error) {
  412. for _, file := range s.Files {
  413. if file.Title == key {
  414. return []byte(file.Content), nil
  415. }
  416. }
  417. return nil, fmt.Errorf(errKeeperSecurityInvalidField, key)
  418. }
  419. func (s *Secret) getProperty(key string) ([]byte, error) {
  420. field, _ := s.getField(key)
  421. if field != nil {
  422. return field, nil
  423. }
  424. customField, _ := s.getCustomField(key)
  425. if customField != nil {
  426. return customField, nil
  427. }
  428. file, _ := s.getFile(key)
  429. if file != nil {
  430. return file, nil
  431. }
  432. return nil, fmt.Errorf(errKeeperSecurityInvalidProperty, s.Title, key)
  433. }
  434. func (s *Secret) getFiles() map[string][]byte {
  435. secretData := make(map[string][]byte)
  436. for _, file := range s.Files {
  437. secretData[file.Title] = []byte(file.Content)
  438. }
  439. return secretData
  440. }
  441. func (s *Secret) toString() (string, error) {
  442. secretJSON, err := json.Marshal(s)
  443. if err != nil {
  444. return "", fmt.Errorf(errInvalidJSONSecret, s.Title, err)
  445. }
  446. return string(secretJSON), nil
  447. }