Skip to content

Commit

Permalink
refactor(kubernetes-secret): collapse rules and update regex
Browse files Browse the repository at this point in the history
  • Loading branch information
rgmz committed Sep 17, 2024
1 parent 128cd22 commit cc316b0
Show file tree
Hide file tree
Showing 3 changed files with 314 additions and 58 deletions.
3 changes: 1 addition & 2 deletions cmd/generate/config/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,7 @@ func main() {
rules.JWT(),
rules.JWTBase64(),
rules.KrakenAccessToken(),
rules.KubernetesSecretWithDataAfter(),
rules.KubernetesSecretWithDataBefore(),
rules.KubernetesSecret(),
rules.KucoinAccessToken(),
rules.KucoinSecretKey(),
rules.LaunchDarklyAccessToken(),
Expand Down
356 changes: 311 additions & 45 deletions cmd/generate/config/rules/kubernetes.go
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)
}
Loading

0 comments on commit cc316b0

Please sign in to comment.