-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(kubernetes-secret): collapse rules and update regex
- Loading branch information
Showing
3 changed files
with
314 additions
and
58 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,69 +1,335 @@ | ||
package rules | ||
|
||
import ( | ||
"github.com/zricethezav/gitleaks/v8/cmd/generate/config/utils" | ||
"fmt" | ||
"regexp" | ||
|
||
"github.com/zricethezav/gitleaks/v8/cmd/generate/config/utils" | ||
"github.com/zricethezav/gitleaks/v8/config" | ||
) | ||
|
||
// The kubernetes rules are split into two functions to make the complex proximity matching of the data-key and the kind-identifier more readable and testable | ||
// KubernetesSecret validates if we detected a kubernetes secret which contains data! | ||
func KubernetesSecret() *config.Rule { | ||
// Only match basic variations of `kind: secret`, we don't want things like `kind: ExternalSecret`. | ||
//language=regexp | ||
kindPat := `\bkind:[ \t]*["']?secret["']?` | ||
// Only matches values (`key: value`) under `data:` that are: | ||
// - valid base64 characters | ||
// - longer than 10 characters (no "YmFyCg==") | ||
//language=regexp | ||
dataPat := `\bdata:(?:.|\s){0,100}?\s+([\w.-]+:(?:[ \t]*(?:\||>[-+]?)\s+)?[ \t]*["']?[a-z0-9]{10,}={0,3})["']?` | ||
|
||
// KubernetesSecretWithDataBefore validates if we detected a kubernetes secret which contains data, before the resource identifier! | ||
func KubernetesSecretWithDataBefore() *config.Rule { | ||
// define rule | ||
r := config.Rule{ | ||
RuleID: "kubernetes-secret-with-data-before", | ||
RuleID: "kubernetes-secret", | ||
Description: "Possible Kubernetes Secret detected, posing a risk of leaking credentials/tokens from your deployments", | ||
// We try to match secrets by looking if we have the keyword | ||
Regex: utils.GenerateUniqueTokenRegex(`(?i)(?:\b(?:data:))(\W+(?:\w+\W+){0,200}?)\bkind:.{0,10}Secret\b`, true), | ||
|
||
Regex: regexp.MustCompile(fmt.Sprintf( | ||
//language=regexp | ||
`(?i)(?:%s(?:.|\s){0,200}?%s|%s(?:.|\s){0,200}?%s)`, kindPat, dataPat, dataPat, kindPat)), | ||
Keywords: []string{ | ||
"Secret", | ||
"secret", | ||
}, | ||
// Kubernetes secrets are always yaml files, we limit to common yaml-endings to make this rule more safe! | ||
// Kubernetes secrets are usually yaml files. | ||
Path: regexp.MustCompile(`(?i)\.ya?ml$`), | ||
} | ||
|
||
// validate | ||
tps := map[string]string{ | ||
// The "data"-key is before the identifier "kind: Secret" | ||
"before-kubernetes.yaml": `apiVersion: v1 | ||
data: | ||
extra: YWRtaW46cGFzc3dvcmQ= | ||
kind: secret | ||
metadata: | ||
name: secret-sa-sample | ||
annotations: | ||
kubernetes.io/service-account.name: 'sa-name'`, | ||
"before-kubernetes.yml": `apiVersion: v1 | ||
data: | ||
password: UyFCXCpkJHpEc2I9 | ||
username: YWRtaW4= | ||
kind: Secret | ||
metadata: | ||
creationTimestamp: '2022-06-28T17:44:13Z' | ||
name: db-user-pass | ||
namespace: default | ||
type: Opaque`, | ||
"before-comment.yml": `apiVersion: v1 | ||
data: | ||
# the data is abbreviated in this example | ||
password: UyFCXCpkJHpEc2I9 | ||
username: YWRtaW4= | ||
kind: Secret | ||
metadata: | ||
creationTimestamp: '2022-06-28T17:44:13Z' | ||
name: db-user-pass | ||
namespace: default | ||
type: Opaque`, | ||
"before-quoted-1.yaml": `apiVersion: 'v1' | ||
data: | ||
extra: 'YWRtaW46cGFzc3dvcmQ=' | ||
kind: 'Secret' | ||
metadata: | ||
name: 'secret-sa-sample' | ||
annotations: | ||
kubernetes.io/service-account.name: 'sa-name'`, | ||
"before-quoted-2.yaml": `apiVersion: "v1" | ||
data: | ||
extra: "YWRtaW46cGFzc3dvcmQ=" | ||
kind: "secret" | ||
metadata: | ||
name: "secret-sa-sample" | ||
annotations: | ||
kubernetes.io/service-account.name: "sa-name"`, | ||
"before-multiline-literal.yaml": `apiVersion: v1 | ||
data: | ||
.dockercfg: | | ||
eyJhdXRocyI6eyJodHRwczovL2V4YW1wbGUvdjEvIjp7ImF1dGgiOiJvcGVuc2VzYW1lIn19fQo= | ||
metadata: | ||
name: secret-dockercfg | ||
type: kubernetes.io/dockercfg | ||
kind: Secret | ||
`, | ||
"before-multiline-folded.yaml": `apiVersion: v1 | ||
data: | ||
.dockercfg: > | ||
eyJhdXRocyI6eyJodHRwczovL2V4YW1wbGUvdjEvIjp7ImF1dGgiOiJvcGVuc2VzYW1lIn19fQo= | ||
metadata: | ||
name: secret-dockercfg | ||
type: kubernetes.io/dockercfg | ||
kind: Secret`, | ||
// Sample Kubernetes Secret from https://kubernetes.io/docs/concepts/configuration/secret/ | ||
// These secrets contain the "data"-key before the actual identifier "kind: Secret" | ||
"kubernetes.yaml": "apiVersion: v1'\n' data:'\n' extra: YmFyCg=='\n' kind: secret'\n' metadata:'\n' name: secret-sa-sample'\n' annotations:'\n' kubernetes.io/service-account.name: 'sa-name'", // gitleaks:allow | ||
"kubernetes.yml": "apiVersion: v1'\n' data:'\n' password: UyFCXCpkJHpEc2I9'\n' username: YWRtaW4='\n' kind: Secret'\n' metadata:'\n' creationTimestamp: '2022-06-28T17:44:13Z''\n' name: db-user-pass'\n' namespace: default'\n' type: Opaque", // gitleaks:allow | ||
// Quoted Test Cases | ||
"kubernetes-quoted-1.yaml": "apiVersion: v1'\n' data:'\n' extra: YmFyCg=='\n' kind: 'Secret''\n' metadata:'\n' name: 'secret-sa-sample''\n' annotations:'\n' kubernetes.io/service-account.name: 'sa-name'", // gitleaks:allow | ||
"kubernetes-quoted-2.yaml": "apiVersion: v1'\n' data:'\n' extra: YmFyCg=='\n' kind: 'secret''\n' metadata:'\n' name: 'secret-sa-sample''\n' annotations:'\n' kubernetes.io/service-account.name: 'sa-name'", // gitleaks:allow | ||
// The "data"-key is after the identifier "kind: Secret" | ||
"after-kubernetes.yaml": `apiVersion: v1 | ||
kind: secret | ||
data: | ||
extra: YWRtaW46cGFzc3dvcmQ= | ||
metadata: | ||
name: secret-sa-sample | ||
annotations: | ||
kubernetes.io/service-account.name: 'sa-name'`, | ||
"after-kubernetes.yml": `apiVersion: v1 | ||
kind: Secret | ||
data: | ||
password: UyFCXCpkJHpEc2I9 | ||
username: YWRtaW4= | ||
metadata: | ||
creationTimestamp: '2022-06-28T17:44:13Z' | ||
name: db-user-pass | ||
namespace: default | ||
type: Opaque`, | ||
"after-comment.yml": `apiVersion: v1 | ||
kind: Secret | ||
metadata: | ||
creationTimestamp: '2022-06-28T17:44:13Z' | ||
name: db-user-pass | ||
namespace: default | ||
type: Opaque | ||
data: | ||
# the data is abbreviated in this example | ||
password: UyFCXCpkJHpEc2I9 | ||
username: YWRtaW4= | ||
`, | ||
"after-quoted-1.yaml": `apiVersion: 'v1' | ||
kind: 'Secret' | ||
data: | ||
password: 'UyFCXCpkJHpEc2I9' | ||
username: 'YWRtaW4=' | ||
metadata: | ||
name: 'db-user-pass' | ||
namespace: 'default' | ||
type: 'Opaque'`, | ||
"after-quoted-2.yaml": `apiVersion: "v1" | ||
kind: "Secret" | ||
data: | ||
password: "UyFCXCpkJHpEc2I9" | ||
username: "YWRtaW4=" | ||
metadata: | ||
name: "db-user-pass" | ||
namespace: "default" | ||
type: "Opaque"`, | ||
"after-multiline-literal.yaml": `apiVersion: v1 | ||
kind: Secret | ||
metadata: | ||
name: secret-dockercfg | ||
type: kubernetes.io/dockercfg | ||
data: | ||
.dockercfg: | | ||
eyJhdXRocyI6eyJodHRwczovL2V4YW1wbGUvdjEvIjp7ImF1dGgiOiJvcGVuc2VzYW1lIn19fQo= | ||
`, | ||
"after-multiline-folded.yaml": `apiVersion: v1 | ||
kind: Secret | ||
metadata: | ||
name: secret-dockercfg | ||
type: kubernetes.io/dockercfg | ||
data: | ||
.dockercfg: > | ||
eyJhdXRocyI6eyJodHRwczovL2V4YW1wbGUvdjEvIjp7ImF1dGgiOiJvcGVuc2VzYW1lIn19fQo=`, | ||
} | ||
return utils.ValidateWithPaths(r, tps, nil) | ||
} | ||
fps := map[string]string{ | ||
"sopssecret.yaml": `apiVersion: isindir.github.com/v1alpha3 | ||
kind: SopsSecret | ||
metadata: | ||
name: app1-sopssecret | ||
namespace: test | ||
spec: | ||
suspend: false | ||
secretTemplates: | ||
- name: ENC[AES256_GCM,data:W3PiZ6lD6bpfAdI=,iv:2qF98ZkchgfWF4tZo8fok6zY0ZLNRV3wFpl8n2iyC7I=,tag:FzoL+CZHkLEqfWKniRApBA==,type:str] | ||
labels: | ||
app: ENC[AES256_GCM,data:t9ujIQ==,iv:slZBpmKF+DOg/wVBWmq5iTqkRBZUMao0a3MdoxzJs3s=,tag:xJyhdJ4rn/cB/4mxHzmGig==,type:str] | ||
stringData: | ||
db-password: ENC[AES256_GCM,data:O+5l4g==,iv:c/dS4BCBMbnKsXYuzBuCuVQt8RV9bOv5HgdpL+iwmns=,tag:KkQfh6OymvCt4uC13p318g==,type:str] | ||
sops: | ||
kms: [] | ||
gcp_kms: [] | ||
azure_kv: [] | ||
hc_vault: [] | ||
lastmodified: '2021-10-21T10:56:37Z' | ||
mac: ENC[AES256_GCM,data:Tl1V1PuI5tZ0Hu3qxxzpDNeKQkuW0g/x/Mlp1yM6HaBqDr+r2FukLdYSYqjjJ3A8g+YkpvMib50M7j0V7zoX9sgCvMKEg86pRsWtThv8n/L+bsjClVTqnhJ9nfYlaPOMlvggbiMOE5hXPIuVz8WXYoVYJ2cVNCd/GfwOraUmj7I=,iv:n7HVI13okfbW3FS/ZsJ2GNmibudxc/TlkLa3umQQ+vc=,tag:R4ikep1wlxlDWlODJqFHHw==,type:str] | ||
pgp: | ||
- created_at: '2021-10-21T10:56:37Z' | ||
enc: | | ||
-----BEGIN PGP MESSAGE----- | ||
// KubernetesSecretWithDataAfter validates if we detected a kubernetes secret which contains data, after the resource identifier! | ||
func KubernetesSecretWithDataAfter() *config.Rule { | ||
// define rule | ||
r := config.Rule{ | ||
RuleID: "kubernetes-secret-with-data-after", | ||
Description: "Possible Kubernetes Secret detected, posing a risk of leaking credentials/tokens from your deployments", | ||
// We try to match secrets by looking if we have the keyword | ||
Regex: utils.GenerateUniqueTokenRegex(`(?i)(?:\bkind:.{0,10}Secret\b)(?:.|\s){0,200}?\b(?:data:)\s*(.+)`, true), | ||
|
||
Keywords: []string{ | ||
"Secret", | ||
}, | ||
// Kubernetes secrets are always yaml files, we limit to common yaml-endings to make this rule more safe! | ||
Path: regexp.MustCompile(`(?i)\.ya?ml$`), | ||
} | ||
|
||
// validate | ||
tps := map[string]string{ | ||
// Sample Kubernetes Secret from https://kubernetes.io/docs/concepts/configuration/secret/ | ||
// These secrets contain the data after the actual identifier "kind: Secret" | ||
"kubernetes.yaml": "apiVersion: v1'\n' kind: secret'\n' data:'\n' extra: YmFyCg=='\n' metadata:'\n' name: secret-sa-sample'\n' annotations:'\n' kubernetes.io/service-account.name: 'sa-name'", // gitleaks:allow | ||
"kubernetes.yml": "apiVersion: v1'\n' kind: Secret'\n' data:'\n' password: UyFCXCpkJHpEc2I9'\n' username: YWRtaW4='\n' metadata:'\n' creationTimestamp: '2022-06-28T17:44:13Z''\n' name: db-user-pass'\n' namespace: default'\n' type: Opaque", // gitleaks:allow | ||
// Quoted Test Cases | ||
"kubernetes-quoted-1.yaml": "apiVersion: v1'\n' kind: 'Secret''\n' data:'\n' password: UyFCXCpkJHpEc2I9'\n' username: YWRtaW4='\n' metadata:'\n' name: db-user-pass'\n' namespace: default'\n' type: Opaque", // gitleaks:allow | ||
"kubernetes-quoted-2.yaml": "apiVersion: v1'\n' kind: 'secret''\n' data:'\n' password: UyFCXCpkJHpEc2I9'\n' username: YWRtaW4='\n' metadata:'\n' name: db-user-pass'\n' namespace: default'\n' type: Opaque", // gitleaks:allow | ||
hQGMA3muqimBu2IIAQwAkhR19/6roLq06oaD12vqDMes3/8FweAHxa6TLKg+LRjp | ||
2/ntiRJHPBP9DYYFZbkTo8lAmIdVF7KfGIqWgPm5JiNhqfVRhyGPCRgBE7+I8qH6 | ||
EML9Vo/76kJLHtIjs5rOg7OXgwwitaibs1q6uyVY8TuaGXYIOO1iwL9xVtbayIry | ||
NMQd1tFcNb6Vb86Plqm+T1VnSOJMUvryxrLelx89UNM0ctepyVu6YY9jpBjV0QLJ | ||
NqNkKAGIMv3RNa9bZHTwveo9T0oXtFnk5H33BxH0ky/DGpD+5Ch1YgbzbqVnr+Bm | ||
RX0R/GRhS9IDInd+eiyVX6y3LR5di0fc8TuK43+96wTG+2+ck+lbMrkHYsL2UJNv | ||
bAjlOWmIcL4UwGlEOj4EzwcEx+xP3dq57pJ+DasfNwVqps2Kk+ofodR7d6gx7ELH | ||
UQmLypCtkRic9v8fVSA2vEL8hAlg9bT8tpHLhHMOwe228cL5dTzFD60RoP+ovRar | ||
jIU59Pnu1bnM4pXWEVA20l4BzJ8Fd6gj3TfAg/7Mat+dnTaUwnPgRSybFn0ZZHMW | ||
RJDBPkMGFfSGRDfLeD37d61mI31360/w/61LaVp1sdDYodBJCRZFA1YzbqZcxnDl | ||
YRjRmpcVRnO+o72CnU/P | ||
=V4l4 | ||
-----END PGP MESSAGE----- | ||
fp: 73019E949C1D3C3D1BE8B718C7CD51A565AB592C | ||
encrypted_suffix: Templates | ||
version: 3.6.1`, // https://github.com/luca-iachini/argocd-test/blob/af0c8eaba270bc918108c8bc3b909f26a4fe995d/kustomize/base/app1/secrets.enc.yaml#L4 | ||
// The "data"-key is before the identifier "kind: Secret" | ||
"before-min-length.yaml": `apiVersion: v1 | ||
data: | ||
extra: YmFyCg== | ||
kind: secret | ||
metadata: | ||
name: secret-sa-sample | ||
annotations: | ||
kubernetes.io/service-account.name: 'sa-name'`, | ||
"before-template.yaml": `apiVersion: v1 | ||
data: | ||
password: {{ .Values.Password }} | ||
kind: secret | ||
metadata: | ||
name: secret-sa-sample | ||
annotations: | ||
kubernetes.io/service-account.name: 'sa-name'`, | ||
"before-externalsecret1.yml": `apiVersion: 'kubernetes-client.io/v1' | ||
metadata: | ||
name: actions-exporter | ||
namespace: github-actions-exporter | ||
spec: | ||
backendType: secretsManager | ||
data: | ||
- key: MySecretManagerKey | ||
name: github_token | ||
property: github_token | ||
kind: ExternalSecret | ||
`, | ||
"before-externalsecret2.yml": `apiVersion: external-secrets.io/v1beta1 | ||
spec: | ||
secretStoreRef: | ||
kind: ClusterSecretStore | ||
name: aws-secretsmanager | ||
refreshInterval: 1h | ||
target: | ||
creationPolicy: Owner | ||
data: | ||
- secretKey: api-key | ||
remoteRef: | ||
key: my-secrets-manager-secret | ||
metadata: | ||
name: api-key | ||
namespace: my-namespace | ||
kind: ExternalSecret | ||
`, | ||
"before-sopssecret.yml": `apiVersion: isindir.github.com/v1alpha3 | ||
spec: | ||
secretTemplates: | ||
- name: my-secret-name-1 | ||
labels: | ||
label1: value1 | ||
annotations: | ||
key1: value1 | ||
data: | ||
data-name1: ZGF0YS12YWx1ZTE= | ||
data-nameM: ZGF0YS12YWx1ZU0= | ||
kind: SopsSecret | ||
metadata: | ||
name: sopssecret-sample | ||
`, // https://github.com/isindir/sops-secrets-operator/blob/8aaf8bb368dc841a2d57f251bd839f08216a9328/config/samples/isindir_v1alpha3_sopssecret.yaml#L4 | ||
// The "data"-key is after the identifier "kind: Secret" | ||
"after-min-length.yaml": `apiVersion: v1 | ||
kind: secret | ||
data: | ||
extra: YmFyCg== | ||
metadata: | ||
name: secret-sa-sample | ||
annotations: | ||
kubernetes.io/service-account.name: 'sa-name'`, | ||
"after-externalsecret1.yml": `apiVersion: 'kubernetes-client.io/v1' | ||
kind: ExternalSecret | ||
metadata: | ||
name: actions-exporter | ||
namespace: github-actions-exporter | ||
spec: | ||
backendType: secretsManager | ||
data: | ||
- key: MySecretManagerKey | ||
name: github_token | ||
property: github_token | ||
- key: MySecretManagerKey`, | ||
"after-externalsecret2.yml": `apiVersion: external-secrets.io/v1beta1 | ||
kind: ExternalSecret | ||
metadata: | ||
name: api-key | ||
namespace: my-namespace | ||
spec: | ||
secretStoreRef: | ||
kind: ClusterSecretStore | ||
name: aws-secretsmanager | ||
refreshInterval: 1h | ||
target: | ||
creationPolicy: Owner | ||
data: | ||
- secretKey: api-key | ||
remoteRef: | ||
key: my-secrets-manager-secret`, | ||
"after-sopssecret.yml": `apiVersion: isindir.github.com/v1alpha3 | ||
kind: SopsSecret | ||
metadata: | ||
name: sopssecret-sample | ||
spec: | ||
secretTemplates: | ||
- name: my-secret-name-0 | ||
labels: | ||
label0: value0 | ||
labelK: valueK | ||
annotations: | ||
key0: value0 | ||
keyN: valueN | ||
stringData: | ||
data-name0: data-value0 | ||
data-nameL: data-valueL | ||
- name: my-secret-name-1 | ||
labels: | ||
label1: value1 | ||
annotations: | ||
key1: value1 | ||
data: | ||
data-name1: ZGF0YS12YWx1ZTE= | ||
data-nameM: ZGF0YS12YWx1ZU0=`, | ||
} | ||
|
||
return utils.ValidateWithPaths(r, tps, nil) | ||
return utils.ValidateWithPaths(r, tps, fps) | ||
} |
Oops, something went wrong.