kubernetes.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. package rules
  2. import (
  3. "fmt"
  4. "regexp"
  5. "github.com/zricethezav/gitleaks/v8/cmd/generate/config/utils"
  6. "github.com/zricethezav/gitleaks/v8/config"
  7. )
  8. // KubernetesSecret validates if we detected a kubernetes secret which contains data!
  9. func KubernetesSecret() *config.Rule {
  10. // Only match basic variations of `kind: secret`, we don't want things like `kind: ExternalSecret`.
  11. //language=regexp
  12. kindPat := `\bkind:[ \t]*["']?secret["']?`
  13. // Only matches values (`key: value`) under `data:` that are:
  14. // - valid base64 characters
  15. // - longer than 10 characters (no "YmFyCg==")
  16. //language=regexp
  17. dataPat := `\bdata:(?:.|\s){0,100}?\s+([\w.-]+:(?:[ \t]*(?:\||>[-+]?)\s+)?[ \t]*(?:["']?[a-z0-9]{10,}={0,3}["']?|\{\{[ \t\w"|$:=,.-]+}}|""|''))`
  18. // define rule
  19. r := config.Rule{
  20. RuleID: "kubernetes-secret-yaml",
  21. Description: "Possible Kubernetes Secret detected, posing a risk of leaking credentials/tokens from your deployments",
  22. Regex: regexp.MustCompile(fmt.Sprintf(
  23. //language=regexp
  24. `(?i)(?:%s(?:.|\s){0,200}?%s|%s(?:.|\s){0,200}?%s)`, kindPat, dataPat, dataPat, kindPat)),
  25. Keywords: []string{
  26. "secret",
  27. },
  28. // Kubernetes secrets are usually yaml files.
  29. Path: regexp.MustCompile(`(?i)\.ya?ml$`),
  30. Allowlist: config.Allowlist{
  31. Regexes: []*regexp.Regexp{
  32. // Ignore empty or placeholder values.
  33. // variable: {{ .Values.Example }} (https://helm.sh/docs/chart_template_guide/variables/)
  34. // variable: ""
  35. // variable: ''
  36. regexp.MustCompile(`[\w.-]+:(?:[ \t]*(?:\||>[-+]?)\s+)?[ \t]*(?:\{\{[ \t\w"|$:=,.-]+}}|""|'')`),
  37. },
  38. },
  39. }
  40. // validate
  41. tps := map[string]string{
  42. "comment.yaml": `
  43. apiVersion: v1
  44. kind: Secret
  45. metadata:
  46. name: heketi-secret
  47. namespace: default
  48. data:
  49. # base64 encoded password. E.g.: echo -n "mypassword" | base64
  50. key: bXlwYXNzd29yZA==`,
  51. // The "data"-key is before the identifier "kind: Secret"
  52. "before-kubernetes.yaml": `apiVersion: v1
  53. data:
  54. extra: YWRtaW46cGFzc3dvcmQ=
  55. kind: secret
  56. metadata:
  57. name: secret-sa-sample
  58. annotations:
  59. kubernetes.io/service-account.name: 'sa-name'`,
  60. "before-kubernetes.yml": `apiVersion: v1
  61. data:
  62. password: UyFCXCpkJHpEc2I9
  63. username: YWRtaW4=
  64. kind: Secret
  65. metadata:
  66. creationTimestamp: '2022-06-28T17:44:13Z'
  67. name: db-user-pass
  68. namespace: default
  69. type: Opaque`,
  70. "before-comment.yml": `apiVersion: v1
  71. data:
  72. # the data is abbreviated in this example
  73. password: UyFCXCpkJHpEc2I9
  74. username: YWRtaW4=
  75. kind: Secret
  76. metadata:
  77. creationTimestamp: '2022-06-28T17:44:13Z'
  78. name: db-user-pass
  79. namespace: default
  80. type: Opaque`,
  81. "before-quoted-1.yaml": `apiVersion: 'v1'
  82. data:
  83. extra: 'YWRtaW46cGFzc3dvcmQ='
  84. kind: 'Secret'
  85. metadata:
  86. name: 'secret-sa-sample'
  87. annotations:
  88. kubernetes.io/service-account.name: 'sa-name'`,
  89. "before-quoted-2.yaml": `apiVersion: "v1"
  90. data:
  91. extra: "YWRtaW46cGFzc3dvcmQ="
  92. kind: "secret"
  93. metadata:
  94. name: "secret-sa-sample"
  95. annotations:
  96. kubernetes.io/service-account.name: "sa-name"`,
  97. "before-multiline-literal.yaml": `apiVersion: v1
  98. data:
  99. .dockercfg: |
  100. eyJhdXRocyI6eyJodHRwczovL2V4YW1wbGUvdjEvIjp7ImF1dGgiOiJvcGVuc2VzYW1lIn19fQo=
  101. metadata:
  102. name: secret-dockercfg
  103. type: kubernetes.io/dockercfg
  104. kind: Secret
  105. `,
  106. "before-multiline-folded.yaml": `apiVersion: v1
  107. data:
  108. .dockercfg: >
  109. eyJhdXRocyI6eyJodHRwczovL2V4YW1wbGUvdjEvIjp7ImF1dGgiOiJvcGVuc2VzYW1lIn19fQo=
  110. metadata:
  111. name: secret-dockercfg
  112. type: kubernetes.io/dockercfg
  113. kind: Secret`,
  114. // Sample Kubernetes Secret from https://kubernetes.io/docs/concepts/configuration/secret/
  115. // The "data"-key is after the identifier "kind: Secret"
  116. "after-kubernetes.yaml": `apiVersion: v1
  117. kind: secret
  118. data:
  119. extra: YWRtaW46cGFzc3dvcmQ=
  120. metadata:
  121. name: secret-sa-sample
  122. annotations:
  123. kubernetes.io/service-account.name: 'sa-name'`,
  124. "after-kubernetes.yml": `apiVersion: v1
  125. kind: Secret
  126. metadata:
  127. name: ca-secret
  128. type: Opaque
  129. data:
  130. ca.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUR4RENDQXF5Z0F3SUJBZ0lVV3pqUDl5RUk0eHlRSnBzVHVERU4yV2ROaUFzd0RRWUpLb1pJaHZjTkFRRUwKQlFBd2FERUxNQWtHQTFVRUJoTUNWVk14RHpBTkJnTlZCQWdUQms5eVpXZHZiakVSTUE4R0ExVUVCeE1JVUc5eQpkR3hoYm1ReEV6QVJCZ05WQkFvVENrdDFZbVZ5Ym1WMFpYTXhDekFKQmdOVkJBc1RBa05CTVJNd0VRWURWUVFECkV3cExkV0psY201bGRHVnpNQjRYRFRFMk1EZ3hNVEUyTkRnd01Gb1hEVEl4TURneE1ERTJORGd3TUZvd2FERUwKTUFrR0ExVUVCaE1DVlZNeER6QU5CZ05WQkFnVEJrOXlaV2R2YmpFUk1BOEdBMVVFQnhNSVVHOXlkR3hoYm1ReApFekFSQmdOVkJBb1RDa3QxWW1WeWJtVjBaWE14Q3pBSkJnTlZCQXNUQWtOQk1STXdFUVlEVlFRREV3cExkV0psCmNtNWxkR1Z6TUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF3QkhNOGN6anc0Q1cKK05wbklhV012RzZlcVhtelNZT20vbHdaNUhOMnVLck9xaTNHYUUyTjFKd2tzcGRmMXNOUGFZMHdPR2xkbURIZgoxSnlyTW8rUFdLVUVjWko1WGE4Vm02d2I0MlpjczN3MEp5dlEzWFJjaDQyMFJRWGRKayszcmMybWRvSVRkL0lmCnZjWms0N0RzQTMrQU5QSUlSTzdWRmZpS1JNRFpTUDR1OThnVjI2eW1zbjc0TzFVKzNVUHR1TEFTVTFLck9FTk4KR01FWG0ydTJpdmVvbTJrbjFlZTZuM1hCR1o2bU52cUNPdWUxRXdza0gvWkhoUVh1UDgyV1U5dVk0aGVORnoyQwpBNmR0Q0Q0c3Z6eHc3ZFQ2cVhsV0ZIWUYrc3VLVDhXNkczd3NkOWxzV0ZVY0ZWL0lwaTVobEVaTWprNFNoY3RqCjdpYnlrRURKM1FJREFRQUJvMll3WkRBT0JnTlZIUThCQWY4RUJBTUNBUVl3RWdZRFZSMFRBUUgvQkFnd0JnRUIKL3dJQkFqQWRCZ05WSFE0RUZnUVVOdnhRZ3o5ZTNXS2VscU1KTmZXNE1KUHYzc0V3SHdZRFZSMGpCQmd3Rm9BVQpOdnhRZ3o5ZTNXS2VscU1KTmZXNE1KUHYzc0V3RFFZSktvWklodmNOQVFFTEJRQURnZ0VCQUp1TUhYUms1TEVyCmxET1p4Mm9aRUNQZ29reXMzSGJsM05oempXd2pncXdxNVN6a011V3QrUnVkdnRTK0FUQjFtTjRjYTN0eSt2bWcKT09heTkvaDZoditmSE5jZHpYdWR5dFZYZW1KN3F4ZFoxd25DUUcwdnpqOWRZY0xFSGpJWi94dU1jNlY3dnJ4YwpSc0preGp5aE01UXBmRHd0eVZKeGpkUmVBZ0huSyswTkNieHdtQ3cyRGIvOXpudm9LWGk4TEQwbkQzOFQxY3R3CmhmdGxwTmRoZXFNRlpEZXBuTUYwY2g2cHo5TFV5Mkh1cnhrV2dkWVNjY2VNU0hPTzBMcG4xeVVBMWczOTJhUjUKWk81Zm5KMW95Vm1LVWFCeDJCMndsSVlUSXlES1ZiMnY1UXNHbnYvRHVTMDZhcmVLTmsvTGpHRTRlMXlHOHJkcwpacnZHMzNvUmtEbz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
  131. `,
  132. "after-comment.yml": `apiVersion: v1
  133. kind: Secret
  134. metadata:
  135. creationTimestamp: '2022-06-28T17:44:13Z'
  136. name: db-user-pass
  137. namespace: default
  138. type: Opaque
  139. data:
  140. # the data is abbreviated in this example
  141. password: UyFCXCpkJHpEc2I9
  142. username: YWRtaW4=
  143. `,
  144. "after-quoted-1.yaml": `apiVersion: 'v1'
  145. kind: 'Secret'
  146. data:
  147. password: 'UyFCXCpkJHpEc2I9'
  148. username: 'YWRtaW4='
  149. metadata:
  150. name: 'db-user-pass'
  151. namespace: 'default'
  152. type: 'Opaque'`,
  153. "after-quoted-2.yaml": `apiVersion: "v1"
  154. kind: "Secret"
  155. data:
  156. password: "UyFCXCpkJHpEc2I9"
  157. username: "YWRtaW4="
  158. metadata:
  159. name: "db-user-pass"
  160. namespace: "default"
  161. type: "Opaque"`,
  162. "after-multiline-literal.yaml": `apiVersion: v1
  163. kind: Secret
  164. metadata:
  165. name: secret-dockercfg
  166. type: kubernetes.io/dockercfg
  167. data:
  168. .dockercfg: |
  169. eyJhdXRocyI6eyJodHRwczovL2V4YW1wbGUvdjEvIjp7ImF1dGgiOiJvcGVuc2VzYW1lIn19fQo=
  170. `,
  171. "after-multiline-folded.yaml": `apiVersion: v1
  172. kind: Secret
  173. metadata:
  174. name: secret-dockercfg
  175. type: kubernetes.io/dockercfg
  176. data:
  177. .dockercfg: >
  178. eyJhdXRocyI6eyJodHRwczovL2V4YW1wbGUvdjEvIjp7ImF1dGgiOiJvcGVuc2VzYW1lIn19fQo=`,
  179. }
  180. fps := map[string]string{
  181. "empty-quotes1.yml": `apiVersion: v1
  182. kind: Secret
  183. metadata:
  184. name: registry-auth-data
  185. type: Opaque
  186. data:
  187. htpasswd: ''
  188. `,
  189. "empty-quotes2.yml": `apiVersion: v1
  190. kind: Secret
  191. metadata:
  192. name: registry-auth-data
  193. type: Opaque
  194. data:
  195. htpasswd: ""
  196. `,
  197. "overly-permissive1.yaml": `apiVersion: v1
  198. kind: Secret
  199. metadata:
  200. name: registry-auth-data
  201. type: Opaque
  202. data:
  203. htpasswd: {{ htpasswd }}
  204. ---
  205. apiVersion: v1
  206. kind: ReplicationController`,
  207. "overly-permissive2.yaml": `apiVersion: v1
  208. kind: Secret
  209. metadata:
  210. labels:
  211. k8s-app: kubernetes-dashboard
  212. addonmanager.kubernetes.io/mode: EnsureExists
  213. name: kubernetes-dashboard-csrf
  214. namespace: kubernetes-dashboard
  215. type: Opaque
  216. data:
  217. csrf: ""
  218. ---
  219. apiVersion: v1
  220. kind: Secret
  221. metadata:
  222. labels:
  223. k8s-app: kubernetes-dashboard
  224. addonmanager.kubernetes.io/mode: EnsureExists
  225. name: kubernetes-dashboard-key-holder
  226. namespace: kubernetes-dashboard
  227. type: Opaque
  228. `,
  229. "sopssecret.yaml": `apiVersion: isindir.github.com/v1alpha3
  230. kind: SopsSecret
  231. metadata:
  232. name: app1-sopssecret
  233. namespace: test
  234. spec:
  235. suspend: false
  236. secretTemplates:
  237. - name: ENC[AES256_GCM,data:W3PiZ6lD6bpfAdI=,iv:2qF98ZkchgfWF4tZo8fok6zY0ZLNRV3wFpl8n2iyC7I=,tag:FzoL+CZHkLEqfWKniRApBA==,type:str]
  238. labels:
  239. app: ENC[AES256_GCM,data:t9ujIQ==,iv:slZBpmKF+DOg/wVBWmq5iTqkRBZUMao0a3MdoxzJs3s=,tag:xJyhdJ4rn/cB/4mxHzmGig==,type:str]
  240. stringData:
  241. db-password: ENC[AES256_GCM,data:O+5l4g==,iv:c/dS4BCBMbnKsXYuzBuCuVQt8RV9bOv5HgdpL+iwmns=,tag:KkQfh6OymvCt4uC13p318g==,type:str]
  242. sops:
  243. kms: []
  244. gcp_kms: []
  245. azure_kv: []
  246. hc_vault: []
  247. lastmodified: '2021-10-21T10:56:37Z'
  248. mac: ENC[AES256_GCM,data:Tl1V1PuI5tZ0Hu3qxxzpDNeKQkuW0g/x/Mlp1yM6HaBqDr+r2FukLdYSYqjjJ3A8g+YkpvMib50M7j0V7zoX9sgCvMKEg86pRsWtThv8n/L+bsjClVTqnhJ9nfYlaPOMlvggbiMOE5hXPIuVz8WXYoVYJ2cVNCd/GfwOraUmj7I=,iv:n7HVI13okfbW3FS/ZsJ2GNmibudxc/TlkLa3umQQ+vc=,tag:R4ikep1wlxlDWlODJqFHHw==,type:str]
  249. pgp:
  250. - created_at: '2021-10-21T10:56:37Z'
  251. enc: |
  252. -----BEGIN PGP MESSAGE-----
  253. hQGMA3muqimBu2IIAQwAkhR19/6roLq06oaD12vqDMes3/8FweAHxa6TLKg+LRjp
  254. 2/ntiRJHPBP9DYYFZbkTo8lAmIdVF7KfGIqWgPm5JiNhqfVRhyGPCRgBE7+I8qH6
  255. EML9Vo/76kJLHtIjs5rOg7OXgwwitaibs1q6uyVY8TuaGXYIOO1iwL9xVtbayIry
  256. NMQd1tFcNb6Vb86Plqm+T1VnSOJMUvryxrLelx89UNM0ctepyVu6YY9jpBjV0QLJ
  257. NqNkKAGIMv3RNa9bZHTwveo9T0oXtFnk5H33BxH0ky/DGpD+5Ch1YgbzbqVnr+Bm
  258. RX0R/GRhS9IDInd+eiyVX6y3LR5di0fc8TuK43+96wTG+2+ck+lbMrkHYsL2UJNv
  259. bAjlOWmIcL4UwGlEOj4EzwcEx+xP3dq57pJ+DasfNwVqps2Kk+ofodR7d6gx7ELH
  260. UQmLypCtkRic9v8fVSA2vEL8hAlg9bT8tpHLhHMOwe228cL5dTzFD60RoP+ovRar
  261. jIU59Pnu1bnM4pXWEVA20l4BzJ8Fd6gj3TfAg/7Mat+dnTaUwnPgRSybFn0ZZHMW
  262. RJDBPkMGFfSGRDfLeD37d61mI31360/w/61LaVp1sdDYodBJCRZFA1YzbqZcxnDl
  263. YRjRmpcVRnO+o72CnU/P
  264. =V4l4
  265. -----END PGP MESSAGE-----
  266. fp: 73019E949C1D3C3D1BE8B718C7CD51A565AB592C
  267. encrypted_suffix: Templates
  268. version: 3.6.1`, // https://github.com/luca-iachini/argocd-test/blob/af0c8eaba270bc918108c8bc3b909f26a4fe995d/kustomize/base/app1/secrets.enc.yaml#L4
  269. // The "data"-key is before the identifier "kind: Secret"
  270. "before-min-length.yaml": `apiVersion: v1
  271. data:
  272. extra: YmFyCg==
  273. kind: secret
  274. metadata:
  275. name: secret-sa-sample
  276. annotations:
  277. kubernetes.io/service-account.name: 'sa-name'`,
  278. "before-template.yaml": `apiVersion: v1
  279. data:
  280. password: {{ .Values.Password }}
  281. kind: secret
  282. metadata:
  283. name: secret-sa-sample
  284. annotations:
  285. kubernetes.io/service-account.name: 'sa-name'`,
  286. "before-externalsecret1.yml": `apiVersion: 'kubernetes-client.io/v1'
  287. metadata:
  288. name: actions-exporter
  289. namespace: github-actions-exporter
  290. spec:
  291. backendType: secretsManager
  292. data:
  293. - key: MySecretManagerKey
  294. name: github_token
  295. property: github_token
  296. kind: ExternalSecret
  297. `,
  298. "before-externalsecret2.yml": `apiVersion: external-secrets.io/v1beta1
  299. spec:
  300. secretStoreRef:
  301. kind: ClusterSecretStore
  302. name: aws-secretsmanager
  303. refreshInterval: 1h
  304. target:
  305. creationPolicy: Owner
  306. data:
  307. - secretKey: api-key
  308. remoteRef:
  309. key: my-secrets-manager-secret
  310. metadata:
  311. name: api-key
  312. namespace: my-namespace
  313. kind: ExternalSecret
  314. `,
  315. "before-sopssecret.yml": `apiVersion: isindir.github.com/v1alpha3
  316. spec:
  317. secretTemplates:
  318. - name: my-secret-name-1
  319. labels:
  320. label1: value1
  321. annotations:
  322. key1: value1
  323. data:
  324. data-name1: ZGF0YS12YWx1ZTE=
  325. data-nameM: ZGF0YS12YWx1ZU0=
  326. kind: SopsSecret
  327. metadata:
  328. name: sopssecret-sample
  329. `, // https://github.com/isindir/sops-secrets-operator/blob/8aaf8bb368dc841a2d57f251bd839f08216a9328/config/samples/isindir_v1alpha3_sopssecret.yaml#L4
  330. // The "data"-key is after the identifier "kind: Secret"
  331. "after-min-length.yaml": `apiVersion: v1
  332. kind: secret
  333. data:
  334. extra: YmFyCg==
  335. metadata:
  336. name: secret-sa-sample
  337. annotations:
  338. kubernetes.io/service-account.name: 'sa-name'`,
  339. "after-externalsecret1.yml": `apiVersion: 'kubernetes-client.io/v1'
  340. kind: ExternalSecret
  341. metadata:
  342. name: actions-exporter
  343. namespace: github-actions-exporter
  344. spec:
  345. backendType: secretsManager
  346. data:
  347. - key: MySecretManagerKey
  348. name: github_token
  349. property: github_token
  350. - key: MySecretManagerKey`,
  351. "after-externalsecret2.yml": `apiVersion: external-secrets.io/v1beta1
  352. kind: ExternalSecret
  353. metadata:
  354. name: api-key
  355. namespace: my-namespace
  356. spec:
  357. secretStoreRef:
  358. kind: ClusterSecretStore
  359. name: aws-secretsmanager
  360. refreshInterval: 1h
  361. target:
  362. creationPolicy: Owner
  363. data:
  364. - secretKey: api-key
  365. remoteRef:
  366. key: my-secrets-manager-secret`,
  367. "after-sopssecret.yml": `apiVersion: isindir.github.com/v1alpha3
  368. kind: SopsSecret
  369. metadata:
  370. name: sopssecret-sample
  371. spec:
  372. secretTemplates:
  373. - name: my-secret-name-0
  374. labels:
  375. label0: value0
  376. labelK: valueK
  377. annotations:
  378. key0: value0
  379. keyN: valueN
  380. stringData:
  381. data-name0: data-value0
  382. data-nameL: data-valueL
  383. - name: my-secret-name-1
  384. labels:
  385. label1: value1
  386. annotations:
  387. key1: value1
  388. data:
  389. data-name1: ZGF0YS12YWx1ZTE=
  390. data-nameM: ZGF0YS12YWx1ZU0=`,
  391. "after-empty-data.yaml": `apiVersion: v1
  392. kind: Secret
  393. metadata:
  394. labels:
  395. k8s-app: kubernetes-dashboard
  396. addonmanager.kubernetes.io/mode: EnsureExists
  397. name: kubernetes-dashboard-csrf
  398. namespace: kubernetes-dashboard
  399. type: Opaque
  400. data:
  401. csrf: ""
  402. `,
  403. }
  404. return utils.ValidateWithPaths(r, tps, fps)
  405. }