This repository contains a library of security controls that codify Kubernetes best practices derived from the most prevalent security frameworks in the industry. Kubescape uses these controls to scan again running clusters or manifest files under development. They’re written in Rego, the purpose-built declarative policy language that supports Open Policy Agent (OPA).
Framework - a group of controls to test against
Control - a potential vulnerability to check, can include multiple rules
Rule - a single specific test
Add frameworkName.json
file in the /frameworks
directory
Example of a framework:
{
"name": "DevOpsBest",
"description": "This framework is recommended for use by devops.",
"attributes": {
},
"scanningScope": {
"matches": [
"cluster",
"file"
]
},
"controlsNames": [
"Naked pods",
"Container runtime socket mounted",
"Image pull policy on latest tag",
"Label usage for resources",
"K8s common labels usage",
"Pods in default namespace",
"Container hostPort",
"Resources CPU limit and request",
"Resources memory limit and request",
"Configured liveness probe",
"Configured readiness probe"
]
}
scanningScope
- this framework will run just if kubescape scan process match to the scope in the list.(for example the framework above will run if the running kubescape scan is for scanning cluster or file) - list of allowed scanning scope [["cluster", "file"], ["cluster"], ["cloud"], ["GKE"], ["EKS"], ["AKS"]]
. cloud
meaning - will run just on managed clusterAdd controlName.json
file in the /controls
directory.
Example of a control:
{
"name": "Pods in default namespace",
"attributes": {
},
"description": "It is recommended to avoid running pods in cluster without explicit namespace assignment. This control identifies all the pods running in the default namespace.",
"remediation": "Create necessary namespaces and move all the pods from default namespace there.",
"rulesNames": [
"pods-in-default-namespace"
],
"long_description": "It is recommended to avoid running pods in cluster without explicit namespace assignment. This may lead to wrong capabilities and permissions assignment and potential compromises. This control identifies all the pods running in the default namespace.",
"test": "Check that there are no pods in the 'default' namespace",
"id": "C-0061",
"controlID": "C-0061",
"baseScore": 3,
"scanningScope": {
"matches": [
"cluster",
"file"
]
},
"category": {
"name" : "Workload",
"subCategory": {
"name": "Resource management"
}
}
}
rulesNames
- List of rules to run, must be exact name. Use copy-paste to be sure.
scanningScope
- this control will run just if kubescape scan process match to the scope in the list.(for example the control above will run if the running kubescape scan is for scanning cluster or file) - list of allowed scanning scope [["cluster", "file"], ["cluster"], ["cloud"], ["GKE"], ["EKS"], ["AKS"]]
. cloud
meaning - will run just on managed cluster
category
- The category the control belongs to. Some controls may also define a subCategory
. The available categories/sub categories are listed under the mapCategoryNameToID.json
file, mapped to their respective IDs
subCategory
- A sub category for a category
(optional). Must be listed under the mapCategoryNameToID.json
file
long_description
, test
and other control fields are used mainly in the documentation
See control go struct for more control fields
Add to /rules
a new directory with the rule name
Add to the rule directory file - rule.metadata.json
:
Example of rule.metadata.json:
{
"name": "resources-cpu-limit-and-request",
"attributes": {
},
"ruleLanguage": "Rego",
"match": [
{
"apiGroups": [
""
],
"apiVersions": [
"v1"
],
"resources": [
"Pod"
]
}
],
"ruleDependencies": [
],
"controlConfigInputs": [
{
"path": "settings.postureControlInputs.cpu_request_max",
"name": "cpu_request_max",
"description": "Ensure a CPU resource request is set and is under this defined maximum value."
}
],
"description": "CPU limits and requests are not set.",
"remediation": "Ensure CPU limits and requests are set.",
"ruleQuery": "armo_builtins"
}
Optional attributes :
"hostSensorRule": "true"
- indicates the rule gets information from the host scanner
"useFromKubescapeVersion"
- add if rule is only supported from a certain Kubescape version. Inclusive.
"useUntilKubescapeVersion"
- add if a newer version exists so the control doesn’t run both. Inclusive.
"imageScanRelated": true
- indicates that rule uses information from image scanning.
"controlConfigInputs"
- A list the rule uses and can be configured by the user. See example above.
Add to the new rule directory a new file - raw.rego
This is where the logic of the rule is.
Example of raw.rego
:
package armo_builtins
deny[msga] {
pod := input[_]
pod.kind == "Pod"
container := pod.spec.containers[i]
container.securityContext.privileged == true
path := sprintf("containers[%d].securityContext.privileged", [i])
msga := {
"alertMessage": sprintf("pod: %v is defined as privileged", [pod.metadata.name]),
"packagename": "armo_builtins",
"fixPaths": [],
"failedPaths": path,
"alertObject": {
"k8sApiObjects": [pod]
}
}
}
Use opa rego reference for help with syntax
See structure of a rule response
Add a test for the new rule (and run it!). Learn how to add a test here and how to run it here.
Add filter.rego
if needed - If it exists, the filter is run by Kubescape to calculate ‘all resources’ = the number of potential resources to fail. It affects the risk score. This is needed in cases where a rule asks for resources that wil not potentially fail. Example: if a rule asks for pods and service accounts to see if they are connected but only fails the pods, we would create a filter rego that returns only pods.
N.B. To speed up the rule creation, we provided the script scripts/init-rule.py
. This tool for scaffolding and code generation can be used to bootstrap a new rule fast. Let's see an example. To create a new rule, type the command:
python3 scripts/init-rule.py \
--name "ensure-something-is-set" \
--fix-command "chmod 700 /tmp/file" \
--rule-description "this is an example description" \
--rule-remediation "this is an example remediation" \
--alert-message "found something weird" \
--test-list "success,failed_1,failed_2"
This command will create the following directory structure in the regolibrary repository.
rules/ensure-something-is-set/
├── raw.rego
├── rule.metadata.json
└── test
├── failed_1
│ ├── expected.json
│ └── input
├── failed_2
│ ├── expected.json
│ └── input
└── success
├── expected.json
└── input
To have a complete overview about the script, type this command: python3 scripts/init-rule.py --help
.
The Kubescape regolibrary is available as an OPA bundle, for both targets, WASM and Rego.
Endpoint names are normalized to be used as a Rego package name. Here are some examples:
host-pid -> host_pid Host_Ipc -> Host_Ipc foobar -> foobar
To be sure, you can use the following regex to validate the endpoint name:
import re def normalize_rule_name(name) -> str: return re.sub(r'[^a-zA-Z0-9_]', '_', name)
Rules endpoints uses the following naming convention:
data.armo_builtins.rules.<rule_name>.raw.deny
If there is a filter rule, it's available at the following endpoint:
data.armo_builtins.rules.<rule_name>.filter.deny
Controls endpoints uses the following naming convention:
data.armo_builtins.controls.<control_id>.deny
Frameworks endpoints uses the following naming convention:
data.armo_builtins.frameworks.<framework_name>.deny
When evaluating frameworks or controls, you can control the amount of metadata the results will contain by using the data.settings
.
Available settings:
data.settings.verbose
: If set to true
, the evaluation will return a list with an entry for each rule response. Each rule response includes the rule response itself, the control metadata (if evaluated as part of a control), and the framework metadata (if evaluated as part of a framework).
data.settings.metadata
: If set to true
, the evaluation will return a json object with the metadata of the rule, the control (if evaluated as part of a control), or the framework (if evaluated as part of a framework). This json object will have a field named "results"
, with all the lower level results.
When data.settings.verbose
was set to true
, it takes precedence over data.settings.metadata
.
Here is an example of a framework evaluation using the data.settings.metadata
setting:
{
"name": "ArmoBest",
"controlsNames": [...],
"description": "",
// Other framework metadata ...
"results": {
"C-0005": {
"name": "API server insecure port is enabled",
"controlID": "C-0005",
// Other control metadata ...
"results": [
{
"alertMessage": "API server insecure port is enabled",
// Other rule response fields ...
}
]
}
}
}
No settings: If no settings were set, the evaluation will return a list with an entry for each rule response. Each rule response will include only the rule response itself.
The default setting in the released bundles is
data.settings.metadata
.
To build the OPA bundles, use the python script /scripts/bundle.py
.
For example:
python3 scripts/bundle.py . -o release
Some rules and controls are not supported in the OPA bundles, because they require extra customized Rego built-in functions (you can always use Kubescape to evaluate them :wink:).
The following rules are not supported in the OPA bundles:
Reach out if you have any questions:
Thanks to all our contributors! Check out our CONTRIBUTING file to learn how to join them.