@monokle/validation
Advanced tools
Comparing version 0.29.1 to 0.30.0
@@ -6,6 +6,7 @@ import { paramCase, sentenceCase } from 'change-case'; | ||
import unset from 'lodash/unset.js'; | ||
import { isNode } from 'yaml'; | ||
import { isNode, isMap, isScalar, isPair } from 'yaml'; | ||
import { AbstractPlugin } from '../../common/AbstractPlugin.js'; | ||
import { createLocations } from '../../utils/createLocations.js'; | ||
import { isDefined } from '../../utils/isDefined.js'; | ||
import { isContainerPrefix, isPodPrefix } from './utils.js'; | ||
/** | ||
@@ -87,3 +88,3 @@ * Validator for simple custom policies. | ||
const path = args.path.split('.'); | ||
const node = determineClosestNodeForPath(parsedDoc, path); | ||
const node = determineClosestNodeForPath(parsedDoc, path, resource.kind); | ||
const region = node?.range ? this._parser.parseErrorRegion(resource, node.range) : undefined; | ||
@@ -128,9 +129,30 @@ const locations = createLocations(resource, region, path); | ||
* - When $container specifies `securityContext` then it underlines whole context object. | ||
* - When $container does not specify `securityContext` then it underlines whole container object. | ||
* - When $container does not specify `securityContext` then it falls back to the container name. | ||
* - Note: Container objects are quite big and otherwise the whole screen turns yellow/red. | ||
* - When the property does not relate to a container and name cannot be used, it falls back to the whole object. | ||
*/ | ||
function determineClosestNodeForPath(resource, path, prefix = []) { | ||
const currentPath = prefix.concat(path); | ||
while (currentPath.length > prefix.length) { | ||
function determineClosestNodeForPath(resource, path, resourceKind) { | ||
const currentPath = [...path]; | ||
while (currentPath.length > 0) { | ||
const node = resource.getIn(currentPath, true); | ||
if (isNode(node)) { | ||
// Less aggressive error highlighting for missing properties | ||
// Pod objects now fall back to the parent's spec. | ||
// Container objects now fall back to the container's name. | ||
if (isPodPrefix(currentPath, resourceKind)) { | ||
const podPath = currentPath.slice(0, -1); | ||
const parent = resource.getIn(podPath, true); | ||
const spec = isMap(parent) | ||
? parent.items.find(i => (isScalar(i.key) ? i.key.value === 'spec' : false)) | ||
: undefined; | ||
if (isPair(spec) && isNode(spec.key)) { | ||
return spec.key; | ||
} | ||
} | ||
if (isContainerPrefix(currentPath)) { | ||
const nameNode = resource.getIn([...currentPath, 'name'], true); | ||
if (isNode(nameNode)) { | ||
return nameNode; | ||
} | ||
} | ||
return node; | ||
@@ -140,4 +162,3 @@ } | ||
} | ||
const node = resource.getIn(currentPath, true); | ||
return isNode(node) ? node : undefined; | ||
return undefined; | ||
} | ||
@@ -144,0 +165,0 @@ function toPluginMetadata(plugin) { |
import { PodSpec, PodTemplateSpec } from 'kubernetes-types/core/v1.js'; | ||
import { YamlPath } from '../../common/types.js'; | ||
export declare function validatePodSpec(resources: any[], validateFn: (resource: any, pod: PodSpec, prefix: string) => void): void; | ||
export declare function validatePodTemplate(resources: any[], validateFn: (resource: any, pod: PodTemplateSpec, prefix: string) => void): void; | ||
export declare function isContainerPrefix(currentPath: YamlPath): boolean; | ||
export declare function isPodPrefix(currentPath: YamlPath, resourceKind: string): boolean; |
@@ -45,1 +45,16 @@ import { isStatefulSet } from './schemas/statefulset.apps.v1.js'; | ||
} | ||
export function isContainerPrefix(currentPath) { | ||
return currentPath.at(-2) === 'containers'; | ||
} | ||
export function isPodPrefix(currentPath, resourceKind) { | ||
if (resourceKind === 'pod') { | ||
return currentPath.join('.') === 'spec'; | ||
} | ||
if (resourceKind === 'CronJob') { | ||
return currentPath.join('.') === 'spec.jobTemplate.spec.template.spec'; | ||
} | ||
if (['Deployment', 'StatefulSet', 'DaemonSet'].includes(resourceKind)) { | ||
return currentPath.join('.') === 'spec.template.spec'; | ||
} | ||
return false; | ||
} |
@@ -47,2 +47,8 @@ import { PSS_RELATIONS } from '../../../taxonomies/index.js'; | ||
}, | ||
fix({ resource, path }, { unset }) { | ||
unset(resource, path); | ||
return { | ||
description: 'Removes host process for Windows. You might end up with a service with reduced functionality.', | ||
}; | ||
}, | ||
}); |
@@ -26,2 +26,10 @@ import { PSS_RELATIONS } from '../../../taxonomies/index.js'; | ||
}, | ||
fix({ resource, path }, { unset }) { | ||
unset(resource, path); | ||
const isPID = path.includes('hostPID'); | ||
const isIPC = path.includes('hostIPC'); | ||
const remediation = isPID ? 'Removes the host PID.' : isIPC ? 'Removes the host IPC.' : 'Removes the host network.'; | ||
const description = `${remediation} You might end up with a service with reduced functionality.`; | ||
return { description }; | ||
}, | ||
}); |
@@ -41,2 +41,8 @@ import { PSS_RELATIONS } from '../../../taxonomies/index.js'; | ||
}, | ||
fix({ resource, path }, { unset }) { | ||
unset(resource, path); | ||
return { | ||
description: 'Disables usage of privileged pods. You might end up with a service with reduced functionality.', | ||
}; | ||
}, | ||
}); |
@@ -63,2 +63,11 @@ import { PSS_RELATIONS } from '../../../taxonomies/index.js'; | ||
}, | ||
fix({ resource, path, problem }, { get, set }) { | ||
const capabilities = get(resource, path); | ||
if (!Array.isArray(capabilities)) | ||
return; | ||
set(resource, path, capabilities.filter(c => ALLOWED.includes(c))); | ||
return { | ||
description: 'Removes the sys admin capability. You might end up with a degraded service.', | ||
}; | ||
}, | ||
}); |
@@ -25,2 +25,3 @@ import { PSS_RELATIONS } from '../../../taxonomies/index.js'; | ||
}, | ||
fix: undefined, // Autofix makes no sense, they need to decide on another kind of volume. | ||
}); |
@@ -48,2 +48,3 @@ import { NSA_RELATIONS, PSS_RELATIONS } from '../../../taxonomies/index.js'; | ||
}, | ||
fix: undefined, // Autofix makes no sense, they need to decide on another kind of volume. | ||
}); |
@@ -22,2 +22,8 @@ import { PSS_RELATIONS } from '../../../taxonomies/index.js'; | ||
}, | ||
fix({ resource }) { | ||
delete resource?.metadata?.annotations?.['container.apparmor.security.beta.kubernetes.io/*']; | ||
return { | ||
description: 'Removes customization of app armor.', | ||
}; | ||
}, | ||
}); |
@@ -85,2 +85,6 @@ import { PSS_RELATIONS } from '../../../taxonomies/index.js'; | ||
}, | ||
fix({ resource, path }, { unset }) { | ||
unset(resource, path); | ||
return { description: 'Restricts usage of SE Linux.' }; | ||
}, | ||
}); |
@@ -44,2 +44,9 @@ import { PSS_RELATIONS } from '../../../taxonomies/index.js'; | ||
}, | ||
fix({ resource, path }, { get, set }) { | ||
const procMount = get(resource, path); | ||
if (!procMount) | ||
return; | ||
set(resource, path, 'Default'); | ||
return { description: 'Sets the default Proc Mount ' }; | ||
}, | ||
}); |
@@ -51,2 +51,6 @@ import { PSS_RELATIONS } from '../../../taxonomies/index.js'; | ||
}, | ||
fix({ resource, path }, { set }) { | ||
set(resource, path, 'RuntimeDefault'); | ||
return { description: 'Changes profile from Unconfined to RuntimeDefault.' }; | ||
}, | ||
}); |
@@ -31,2 +31,9 @@ import { PSS_RELATIONS } from '../../../taxonomies/index.js'; | ||
}, | ||
fix({ resource, path }, { get, set }) { | ||
const capabilities = get(resource, path); | ||
if (!Array.isArray(capabilities)) | ||
return; | ||
set(resource, path, capabilities.filter(c => ALLOWED.includes(c))); | ||
return { description: 'Removes unsafe sysctl.' }; | ||
}, | ||
}); |
@@ -41,2 +41,3 @@ import { defineRule } from '../../custom/config.js'; | ||
}, | ||
fix: undefined, // Autofix cannot decide the desired allowed volume type. | ||
}); |
@@ -18,3 +18,3 @@ import { CIS_RELATIONS, NSA_RELATIONS, PSS_RELATIONS } from '../../../taxonomies/index.js'; | ||
const allowPrivilegeEscalation = container.securityContext?.allowPrivilegeEscalation; | ||
const valid = allowPrivilegeEscalation === false; | ||
const valid = !allowPrivilegeEscalation; | ||
if (valid) | ||
@@ -28,3 +28,3 @@ return; | ||
const allowPrivilegeEscalation = container.securityContext?.allowPrivilegeEscalation; | ||
const valid = allowPrivilegeEscalation === false; | ||
const valid = !allowPrivilegeEscalation; | ||
if (valid) | ||
@@ -38,3 +38,3 @@ return; | ||
const allowPrivilegeEscalation = container.securityContext?.allowPrivilegeEscalation; | ||
const valid = allowPrivilegeEscalation === false; | ||
const valid = !allowPrivilegeEscalation; | ||
if (valid) | ||
@@ -48,2 +48,6 @@ return; | ||
}, | ||
fix({ resource, path }, { set }) { | ||
set(resource, path, false); | ||
return { description: 'Disables privilege escalation. You might end up with a service with reduced functionality.' }; | ||
}, | ||
}); |
@@ -43,2 +43,6 @@ import { NSA_RELATIONS } from '../../../taxonomies/nsa.js'; | ||
}, | ||
fix({ resource, path }, { set }) { | ||
set(resource, path, true); | ||
return { description: 'Disables running as root. You might end up with a service with reduced functionality.' }; | ||
}, | ||
}); |
@@ -47,2 +47,6 @@ import { NSA_RELATIONS } from '../../../taxonomies/nsa.js'; | ||
}, | ||
fix({ resource, path }, { unset }) { | ||
unset(resource, path); | ||
return { description: 'Removes running as root user. You might end up with a service with reduced functionality.' }; | ||
}, | ||
}); |
@@ -58,2 +58,6 @@ import { PSS_RELATIONS } from '../../../taxonomies/pss.js'; | ||
}, | ||
fix({ resource, path }, { set }) { | ||
set(resource, path, 'RuntimeDefault'); | ||
return { description: 'Changes profile to RuntimeDefault.' }; | ||
}, | ||
}); |
import { PSS_RELATIONS } from '../../../taxonomies/index.js'; | ||
import { defineRule } from '../../custom/config.js'; | ||
import { validatePodSpec } from '../../custom/utils.js'; | ||
const ALLOWED_DROP = 'ALL'; // Any list of capabilities that includes ALL | ||
const REQUIRED_DROP = 'ALL'; // Any list of capabilities that includes ALL | ||
const ALLOWED_ADD = ['NET_BIND_SERVICE']; | ||
@@ -19,4 +19,4 @@ export const capabilitiesStrict = defineRule({ | ||
pod.initContainers?.forEach((container, index) => { | ||
const validDrop = container.securityContext?.capabilities?.drop?.some(c => c === ALLOWED_DROP) ?? false; | ||
if (validDrop) { | ||
const validDrop = container.securityContext?.capabilities?.drop?.some(c => c === REQUIRED_DROP) ?? false; | ||
if (!validDrop) { | ||
report(resource, { | ||
@@ -26,4 +26,4 @@ path: `${prefix}.initContainers.${index}.securityContext.capabilities.drop`, | ||
} | ||
const validAdd = container.securityContext?.capabilities?.add?.every(c => ALLOWED_ADD.includes(c)); | ||
if (validAdd) { | ||
const validAdd = container.securityContext?.capabilities?.add?.every(c => ALLOWED_ADD.includes(c)) ?? true; | ||
if (!validAdd) { | ||
report(resource, { | ||
@@ -35,4 +35,4 @@ path: `${prefix}.initContainers.${index}.securityContext.capabilities.add`, | ||
pod.ephemeralContainers?.forEach((container, index) => { | ||
const validDrop = container.securityContext?.capabilities?.drop?.some(c => c === ALLOWED_DROP) ?? false; | ||
if (validDrop) { | ||
const validDrop = container.securityContext?.capabilities?.drop?.some(c => c === REQUIRED_DROP) ?? false; | ||
if (!validDrop) { | ||
report(resource, { | ||
@@ -42,4 +42,4 @@ path: `${prefix}.ephemeralContainers.${index}.securityContext.capabilities.drop`, | ||
} | ||
const validAdd = container.securityContext?.capabilities?.add?.every(c => ALLOWED_ADD.includes(c)); | ||
if (validAdd) { | ||
const validAdd = container.securityContext?.capabilities?.add?.every(c => ALLOWED_ADD.includes(c)) ?? true; | ||
if (!validAdd) { | ||
report(resource, { | ||
@@ -51,4 +51,4 @@ path: `${prefix}.ephemeralContainers.${index}.securityContext.capabilities.add`, | ||
pod.containers.forEach((container, index) => { | ||
const validDrop = container.securityContext?.capabilities?.drop?.some(c => c === ALLOWED_DROP) ?? false; | ||
if (validDrop) { | ||
const validDrop = container.securityContext?.capabilities?.drop?.some(c => c === REQUIRED_DROP) ?? false; | ||
if (!validDrop) { | ||
report(resource, { | ||
@@ -58,4 +58,4 @@ path: `${prefix}.containers.${index}.securityContext.capabilities.drop`, | ||
} | ||
const validAdd = container.securityContext?.capabilities?.add?.every(c => ALLOWED_ADD.includes(c)); | ||
if (validAdd) { | ||
const validAdd = container.securityContext?.capabilities?.add?.every(c => ALLOWED_ADD.includes(c)) ?? true; | ||
if (!validAdd) { | ||
report(resource, { | ||
@@ -68,2 +68,16 @@ path: `${prefix}.containers.${index}.securityContext.capabilities.add`, | ||
}, | ||
fix({ resource, path }, { get, set }) { | ||
if (path.endsWith('.drop')) { | ||
set(resource, path, ['ALL']); | ||
return { description: 'Drops all capabilities. You might end up with a degraded service.' }; | ||
} | ||
if (path.endsWith('.add')) { | ||
const capabilities = get(resource, path); | ||
if (!Array.isArray(capabilities)) | ||
return; | ||
const allowedCapabilities = capabilities.filter(c => ALLOWED_ADD.includes(c)); | ||
set(resource, path, allowedCapabilities); | ||
return { description: 'Removes disallowed capabilities. You might end up with a degraded service.' }; | ||
} | ||
}, | ||
}); |
@@ -36,5 +36,5 @@ import { CIS_RELATIONS, NSA_RELATIONS } from '../../../taxonomies/index.js'; | ||
fix({ resource, path }, { set }) { | ||
set(resource, path, 'ALL'); | ||
return { description: 'Drops all capabilities.' }; | ||
set(resource, path, ['ALL']); | ||
return { description: 'Drops all capabilities. You might end up with a degraded service.' }; | ||
}, | ||
}); |
{ | ||
"name": "@monokle/validation", | ||
"version": "0.29.1", | ||
"version": "0.30.0", | ||
"description": "Kubernetes resource validation", | ||
@@ -5,0 +5,0 @@ "author": "Kubeshop", |
516265
12854