diff --git a/README.md b/README.md index 1d114e1..e083e92 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,20 @@ $ aks-hc check all -g -n -i ingress-nginx,kube-n $ exit ``` +### Optional - Azure Container Registry + +If you use Azure Container Registry (ACR), you can have this health check review some basic configuration. If will not inspect container images pushed to the registry. + +To do this, look at the container registries available then specify the `--image-registries` option. + +``` bash +$ az acr list --query "[].name" +foo1 +foo2 + +$ aks-hc check all -g -n -i ingress-nginx,kube-node-lease,kube-public,kube-system --image-registries "foo1,foo2" +``` + ## Option B - Run with Azure Service Principal This option walks you through running the health check using an Azure Managed Identity so that it can be tied to a "service principal". Essentially, it avoids impersoning a user or running with someone's identity. diff --git a/modules/disasterRecovery.js b/modules/disasterRecovery.js index 58bbc24..2e84db9 100644 --- a/modules/disasterRecovery.js +++ b/modules/disasterRecovery.js @@ -2,6 +2,9 @@ import chalk from "chalk" import { equalsIgnoreCase } from '../helpers/stringCompare.js'; import { ResultStatus } from '../helpers/commandStatus.js'; import { Severity } from '../helpers/commandSeverity.js'; +import { EOL } from 'os'; + +const space = ' ' // // Checks if the cluster has agent pools without multiple availability zones @@ -10,6 +13,8 @@ export function checkForAvailabilityZones(clusterDetails) { console.log(chalk.white("Checking for agent pools without multiple availability zones...")); + let details = [] + // Find agent pools with either no AZ's or a single AZ var agentPoolsWithNoAzs = clusterDetails .agentPoolProfiles @@ -17,15 +22,30 @@ export function checkForAvailabilityZones(clusterDetails) { // Log output if (agentPoolsWithNoAzs.length) { - console.log(chalk.red(`--- Found ${agentPoolsWithNoAzs.length} agent pools without multiple availability zones`)); + let message = `Found ${agentPoolsWithNoAzs.length} agent pools without multiple availability zones`; + + if (global.verbose) { + agentPoolsWithNoAzs.forEach(x => message += `${EOL}${space}${x.name}`); + } + + details.push({ + status: ResultStatus.Fail, + message: message + } + ); } else { - console.log(chalk.green("--- All agent pools have multiple availability zones")); + details.push({ + status: ResultStatus.Pass, + message: 'All agent pools have multiple availability zones' + } + ); } return { checkId: 'DR-2', - status: !agentPoolsWithNoAzs.length? ResultStatus.Pass: ResultStatus.Fail, - severity: Severity.High + status: !agentPoolsWithNoAzs.length ? ResultStatus.Pass : ResultStatus.Fail, + severity: Severity.High, + details: details } } @@ -36,6 +56,8 @@ export function checkForVelero(pods) { console.log(chalk.white("Checking for Velero...")); + let details = [] + // Check if Velero is installed var veleroInstalled = pods .items @@ -43,15 +65,24 @@ export function checkForVelero(pods) { // Log output if (!veleroInstalled) { - console.log(chalk.red(`--- Velero is not installed`)); + details.push({ + status: ResultStatus.Fail, + message: "Velero is not installed" + } + ); } else { - console.log(chalk.green("--- Velero is installed")); + details.push({ + status: ResultStatus.Pass, + message: "Velero is installed" + } + ); } return { checkId: 'DR-5', - status: veleroInstalled.length? ResultStatus.Pass: ResultStatus.Fail, - severity: Severity.Medium + status: veleroInstalled.length ? ResultStatus.Pass : ResultStatus.Fail, + severity: Severity.Medium, + details: details } } @@ -62,19 +93,30 @@ export function checkForControlPlaneSla(clusterDetails) { console.log(chalk.white("Checking for SLA for control plane...")); + let details = [] + // Check if SLA is configured for the management plane var slaConfigured = clusterDetails.sku.tier == "Paid"; // Log output if (!slaConfigured) { - console.log(chalk.red(`--- An SLA has not been configured for the control plane`)); + details.push({ + status: ResultStatus.Fail, + message: "An SLA has not been configured for the control plane" + } + ); } else { - console.log(chalk.green("--- An SLA has been configured for the control plane")); + details.push({ + status: ResultStatus.Pass, + message: "An SLA has been configured for the control plane" + } + ); } return { checkId: 'DR-6', - status: slaConfigured.length? ResultStatus.Pass: ResultStatus.Fail, - severity: Severity.High + status: slaConfigured.length ? ResultStatus.Pass : ResultStatus.Fail, + severity: Severity.High, + details: details } } \ No newline at end of file diff --git a/modules/imageManagement.js b/modules/imageManagement.js index ca24b92..59942cb 100644 --- a/modules/imageManagement.js +++ b/modules/imageManagement.js @@ -3,7 +3,9 @@ import { equalsIgnoreCase } from '../helpers/stringCompare.js'; import { executeCommand } from '../helpers/commandHelpers.js'; import { ResultStatus } from '../helpers/commandStatus.js'; import { Severity } from '../helpers/commandSeverity.js'; +import { EOL } from 'os'; +const space = ' ' // // Checks for the 'only use allowed images' policy @@ -12,6 +14,8 @@ export function checkForAllowedImages(constraintTemplates) { console.log(chalk.white("Checking for 'Only use allowed images' policy...")); + let details = [] + // Check if allowed images constraint is defined var constraintDefined = constraintTemplates .items @@ -19,15 +23,24 @@ export function checkForAllowedImages(constraintTemplates) { // Log output if (!constraintDefined) { - console.log(chalk.red(`--- 'Only use allowed images' policy not applied`)); + details.push({ + status: ResultStatus.Fail, + message: "Only 'use allowed images' policy not applied" + } + ); } else { - console.log(chalk.green("--- 'Only use allowed images' policy applied")); + details.push({ + status: ResultStatus.Pass, + message: "'Only use allowed images' policy applied" + } + ); } return { checkId: 'IMG-3', - status: constraintDefined.length? ResultStatus.Pass: ResultStatus.Fail, - severity: Severity.High + status: constraintDefined.length ? ResultStatus.Pass : ResultStatus.Fail, + severity: Severity.High, + details: details } } @@ -38,6 +51,8 @@ export function checkForRuntimeContainerSecurity(pods) { console.log(chalk.white("Checking for runtime container security tools...")); + let details = [] + // Determine if Aqua (kube-enforcer) is installed var aquaInstalled = pods .items @@ -57,18 +72,30 @@ export function checkForRuntimeContainerSecurity(pods) { // Otherwise log the tool that was found var knownToolInstalled = aquaInstalled || anchoreInstalled || paloAltoInstalled; if (!knownToolInstalled) { - console.log(chalk.red(`--- A runtime container security tool was not found`)); + details.push({ + status: ResultStatus.Fail, + message: "A runtime container security tool was not found" + } + ); } else { - if (aquaInstalled) console.log(chalk.green(`--- Aqua Kube-Enforcer was found`)); - if (anchoreInstalled) console.log(chalk.green(`--- Anchore Engine was found`)); - if (paloAltoInstalled) console.log(chalk.green(`--- Palo Alto Twistlock was found`)); + let message = ""; + if (aquaInstalled) message = "Aqua Kube-Enforcer was found"; + if (anchoreInstalled) message = "Anchore Engine was found"; + if (paloAltoInstalled) message = "Palo Alto Twistlock was found"; + + details.push({ + status: ResultStatus.Pass, + message: message + } + ); } return { checkId: 'IMG-4', - status: knownToolInstalled? ResultStatus.Pass: ResultStatus.Fail, - severity: Severity.Medium + status: knownToolInstalled ? ResultStatus.Pass : ResultStatus.Fail, + severity: Severity.Medium, + details: details } } @@ -79,6 +106,8 @@ export async function checkForAksAcrRbacIntegration(clusterDetails, containerReg console.log(chalk.white("Checking for ACR/AKS RBAC integration for pulling images...")); + let details = [] + // Grab the kubelet identity from identity profile var identityProfile = clusterDetails.identityProfile; var kubeletIdentityObjectId = identityProfile && identityProfile.kubeletidentity.objectId; @@ -99,14 +128,18 @@ export async function checkForAksAcrRbacIntegration(clusterDetails, containerReg } // Sanity check cluster identity - if (!kubeletIdentityObjectId) { - console.log(chalk.red('--- Could not determine cluster identity. Stopping check.')); - return { - checkId: 'IMG-3', + if (!kubeletIdentityObjectId) { + details.push({ + status: ResultStatus.Pass, + message: "Could not determine cluster identity. Stopping check." + } + ); + return { + checkId: 'IMG-5', status: ResultStatus.Fail, severity: Severity.High } - } + } // Grab the roles for the identity var commandResults = await executeCommand(`az role assignment list --assignee '${kubeletIdentityObjectId}' --all`); @@ -117,20 +150,35 @@ export async function checkForAksAcrRbacIntegration(clusterDetails, containerReg containerRegistries.forEach(registry => { var regEx = new RegExp(`\/registries\/${registry.name}$`); if (!assignedRoles.some(x => x.roleDefinitionName == 'AcrPull' && regEx.test(x.scope))) - problemRegistries.push(registry.name); + problemRegistries.push(registry.name); }); // Log output if (problemRegistries.length) { - console.log(chalk.red(`--- ${problemRegistries.length} registries did not have AKS/ACR RBAC integration`)); + let message = `${problemRegistries.length} registries did not have AKS/ACR RBAC integration`; + + if (global.verbose) { + problemRegistries.forEach(x => message += `${EOL}${space}${x}`); + } + + details.push({ + status: ResultStatus.Fail, + message: message + } + ); } else { - console.log(chalk.green("--- All registries have AKS/ACR RBAC integration")); + details.push({ + status: ResultStatus.Pass, + message: "All registries have AKS/ACR RBAC integration" + } + ); } return { checkId: 'IMG-5', - status: !problemRegistries.length? ResultStatus.Pass: ResultStatus.Fail, - severity: Severity.High + status: !problemRegistries.length ? ResultStatus.Pass : ResultStatus.Fail, + severity: Severity.High, + details: details } } @@ -141,10 +189,15 @@ export function checkForPrivateEndpointsOnRegistries(containerRegistries) { console.log(chalk.white("Checking for private endpoints on container registries...")); + let details = [] + // If there are no container registries the test does not apply if (!containerRegistries.length) { - - console.log(chalk.gray("--- No registries were specified. Skipping check")); + details.push({ + status: ResultStatus.NotApply, + message: "No registries were specified. Skipping check" + } + ); return { checkId: 'IMG-6', @@ -159,15 +212,30 @@ export function checkForPrivateEndpointsOnRegistries(containerRegistries) { // Log output if (problemRegistries.length) { - console.log(chalk.red(`--- ${problemRegistries.length} registries did not have private endpoints configured`)); + let message = `${problemRegistries.length} registries did not have private endpoints configured`; + + if (global.verbose) { + problemRegistries.forEach(x => message += `${EOL}${space}${x.name}`); + } + + details.push({ + status: ResultStatus.Fail, + message: message + } + ); } else { - console.log(chalk.green("--- All registries have private endpoints configured")); + details.push({ + status: ResultStatus.Pass, + message: 'All registries have private endpoints configured' + } + ); } return { checkId: 'IMG-6', - status: !problemRegistries.length? ResultStatus.Pass: ResultStatus.Fail, - severity: Severity.Medium + status: !problemRegistries.length ? ResultStatus.Pass : ResultStatus.Fail, + severity: Severity.Medium, + details: details } } @@ -178,6 +246,8 @@ export function checkForNoPrivilegedContainers(constraintTemplates) { console.log(chalk.white("Checking for 'No privileged containers' policy...")); + let details = [] + // Check if no privileged containers constraint is defined var constraintDefined = constraintTemplates .items @@ -185,14 +255,24 @@ export function checkForNoPrivilegedContainers(constraintTemplates) { // Log output if (!constraintDefined) { - console.log(chalk.red(`--- 'No privileged containers' policy not applied`)); + details.push({ + status: ResultStatus.Fail, + message: "'No privileged containers' policy not applied" + } + ); } else { console.log(chalk.green("--- 'No privileged containers' policy applied")); + details.push({ + status: ResultStatus.Pass, + message: "No privileged containers' policy applied" + } + ); } return { checkId: 'IMG-8', - status: constraintDefined.length? ResultStatus.Pass: ResultStatus.Fail, - severity: Severity.High + status: constraintDefined.length ? ResultStatus.Pass : ResultStatus.Fail, + severity: Severity.High, + details: details } } \ No newline at end of file