diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..0d03a58 --- /dev/null +++ b/.flake8 @@ -0,0 +1,2 @@ +[flake8] +extend-ignore = E501 \ No newline at end of file diff --git a/hardeneks/cluster_wide/reliability/applications.py b/hardeneks/cluster_wide/reliability/applications.py index 22c432c..bd36beb 100644 --- a/hardeneks/cluster_wide/reliability/applications.py +++ b/hardeneks/cluster_wide/reliability/applications.py @@ -17,7 +17,12 @@ def check_metrics_server_is_running(resources: Resources): if "metrics-server" in services: return True else: - print(Panel("[red]Deploy metrics server.")) + print( + Panel( + "[red]Deploy metrics server.", + subtitle="Link: https://aws.github.io/aws-eks-best-practices/reliability/docs/application/#run-kubernetes-metrics-server", + ) + ) console.print() return False @@ -33,6 +38,11 @@ def check_vertical_pod_autoscaler_exists(resources: Resources): if "vpa-recommender" in deployments: return True else: - print(Panel("[red]Deploy vertical pod autoscaler if needed.")) + print( + Panel( + "[red]Deploy vertical pod autoscaler if needed.", + subtitle="Link: https://aws.github.io/aws-eks-best-practices/reliability/docs/application/#vertical-pod-autoscaler-vpa", + ) + ) console.print() return False diff --git a/hardeneks/cluster_wide/security/detective_controls.py b/hardeneks/cluster_wide/security/detective_controls.py index 60f1928..24fbcb2 100644 --- a/hardeneks/cluster_wide/security/detective_controls.py +++ b/hardeneks/cluster_wide/security/detective_controls.py @@ -16,7 +16,12 @@ def check_logs_are_enabled(resources: Resources): "enabled" ] if not logs: - print(Panel("[red]Enable control plane logs for auditing")) + print( + Panel( + "[red]Enable control plane logs for auditing", + subtitle="Link: https://aws.github.io/aws-eks-best-practices/security/docs/detective/#enable-audit-logs", + ) + ) console.print() return logs diff --git a/hardeneks/cluster_wide/security/encryption_secrets.py b/hardeneks/cluster_wide/security/encryption_secrets.py index 7bd088d..6c1d227 100644 --- a/hardeneks/cluster_wide/security/encryption_secrets.py +++ b/hardeneks/cluster_wide/security/encryption_secrets.py @@ -17,6 +17,7 @@ def use_encryption_with_ebs(resources: Resources): print_storage_class_table( offenders, "[red]EBS Storage Classes should have encryption parameter", + "Link: https://aws.github.io/aws-eks-best-practices/security/docs/data/#encryption-at-rest", ) return offenders @@ -38,6 +39,7 @@ def use_encryption_with_efs(resources: Resources): print_persistent_volume_table( offenders, "[red]EFS Persistent volumes should have tls mount option", + "Link: https://aws.github.io/aws-eks-best-practices/security/docs/data/#encryption-at-rest", ) return offenders @@ -55,5 +57,6 @@ def use_efs_access_points(resources: Resources): print_persistent_volume_table( offenders, "[red]EFS Persistent volumes should leverage access points", + "Link: https://aws.github.io/aws-eks-best-practices/security/docs/data/#use-efs-access-points-to-simplify-access-to-shared-datasets", ) return offenders diff --git a/hardeneks/cluster_wide/security/iam.py b/hardeneks/cluster_wide/security/iam.py index 70decdd..ea5aee0 100644 --- a/hardeneks/cluster_wide/security/iam.py +++ b/hardeneks/cluster_wide/security/iam.py @@ -24,6 +24,7 @@ def restrict_wildcard_for_cluster_roles(resources: Resources): print_role_table( offenders, "[red]ClusterRoles should not have '*' in Verbs or Resources", + "Link: https://aws.github.io/aws-eks-best-practices/security/docs/iam/#employ-least-privileged-access-when-creating-rolebindings-and-clusterrolebindings", "ClusterRole", ) return offenders @@ -36,7 +37,12 @@ def check_endpoint_public_access(resources: Resources): "endpointPublicAccess" ] if endpoint_access: - print(Panel("[red]EKS Cluster Endpoint is not Private")) + print( + Panel( + "[red]EKS Cluster Endpoint is not Private", + subtitle="Link: https://aws.github.io/aws-eks-best-practices/security/docs/iam/#make-the-eks-cluster-endpoint-private", + ) + ) console.print() return False @@ -49,7 +55,12 @@ def check_aws_node_daemonset_service_account(resources: Resources): ) if daemonset.spec.template.spec.service_account_name == "aws-node": - print(Panel("[red]Update the aws-node daemonset to use IRSA")) + print( + Panel( + "[red]Update the aws-node daemonset to use IRSA", + subtitle="Link: https://aws.github.io/aws-eks-best-practices/security/docs/iam/#update-the-aws-node-daemonset-to-use-irsa", + ) + ) console.print() return False @@ -84,6 +95,7 @@ def check_access_to_instance_profile(resources: Resources): print_instance_metadata_table( offenders, "[red]Restrict access to the instance profile assigned to nodes", + "Link: https://aws.github.io/aws-eks-best-practices/security/docs/iam/#when-your-application-needs-access-to-imds-use-imdsv2-and-increase-the-hop-limit-on-ec2-instances-to-2", ) return offenders @@ -104,6 +116,7 @@ def disable_anonymous_access_for_cluster_roles(resources: Resources): print_role_table( offenders, "[red]Don't bind clusterroles to anonymous/unauthenticated groups", + "Link: https://aws.github.io/aws-eks-best-practices/security/docs/iam/#review-and-revoke-unnecessary-anonymous-access", "ClusterRoleBinding", ) diff --git a/hardeneks/cluster_wide/security/image_security.py b/hardeneks/cluster_wide/security/image_security.py index 89a53b9..28e1f49 100644 --- a/hardeneks/cluster_wide/security/image_security.py +++ b/hardeneks/cluster_wide/security/image_security.py @@ -18,6 +18,7 @@ def use_immutable_tags_with_ecr(resources: Resources): offenders, "imageTagMutability", "[red]Make image tags immutable.", + "Link: https://aws.github.io/aws-eks-best-practices/security/docs/image/#use-immutable-tags-with-ecr", ) return offenders diff --git a/hardeneks/cluster_wide/security/infrastructure_security.py b/hardeneks/cluster_wide/security/infrastructure_security.py index feb4df6..9caec37 100644 --- a/hardeneks/cluster_wide/security/infrastructure_security.py +++ b/hardeneks/cluster_wide/security/infrastructure_security.py @@ -34,6 +34,7 @@ def deploy_workers_onto_private_subnets(resources: Resources): print_instance_public_table( offenders, "[red]Place worker nodes on private subnets.", + "Link: https://aws.github.io/aws-eks-best-practices/security/docs/hosts/#deploy-workers-onto-private-subnets", ) return offenders @@ -55,7 +56,12 @@ def make_sure_inspector_is_enabled(resources: Resources): ecr_status = resource_state["ecr"]["status"] if ec2_status != "ENABLED" and ecr_status != "ENABLED": - print(Panel("[red]Enable Amazon Inspector for ec2 and ecr")) + print( + Panel( + "[red]Enable Amazon Inspector for ec2 and ecr", + subtitle="Link: https://aws.github.io/aws-eks-best-practices/security/docs/hosts/#run-amazon-inspector-to-assess-hosts-for-exposure-vulnerabilities-and-deviations-from-best-practices", + ) + ) console.print() return False diff --git a/hardeneks/cluster_wide/security/multi_tenancy.py b/hardeneks/cluster_wide/security/multi_tenancy.py index c18f188..aab9e3d 100644 --- a/hardeneks/cluster_wide/security/multi_tenancy.py +++ b/hardeneks/cluster_wide/security/multi_tenancy.py @@ -20,6 +20,7 @@ def ensure_namespace_quotas_exist(resources: Resources): print_namespace_table( offenders, "[red]Namespaces should have quotas assigned", + "Link: https://aws.github.io/aws-eks-best-practices/security/docs/multitenancy/#namespaces", ) return offenders diff --git a/hardeneks/cluster_wide/security/network_security.py b/hardeneks/cluster_wide/security/network_security.py index bb53234..d2deda9 100644 --- a/hardeneks/cluster_wide/security/network_security.py +++ b/hardeneks/cluster_wide/security/network_security.py @@ -24,7 +24,12 @@ def check_vpc_flow_logs(resources: Resources): )["FlowLogs"] if not flow_logs: - print(Panel("[red]Enable flow logs for your VPC.")) + print( + Panel( + "[red]Enable flow logs for your VPC.", + subtitle="Link: https://aws.github.io/aws-eks-best-practices/security/docs/network/#log-network-traffic-metadata", + ) + ) console.print() return False @@ -38,6 +43,7 @@ def check_awspca_exists(resources: Resources): print( Panel( "[red]Install aws privateca issuer for your certificates.", + subtitle="Link: https://aws.github.io/aws-eks-best-practices/security/docs/network/#acm-private-ca-with-cert-manager", ) ) console.print() @@ -54,6 +60,7 @@ def check_default_deny_policy_exists(resources: Resources): print_namespace_table( offenders, "[red]Namespaces that does not have default network deny policies", + "Link: https://aws.github.io/aws-eks-best-practices/security/docs/network/#create-a-default-deny-policy", ) return offenders diff --git a/hardeneks/cluster_wide/security/pod_security.py b/hardeneks/cluster_wide/security/pod_security.py index fcedc77..8618686 100644 --- a/hardeneks/cluster_wide/security/pod_security.py +++ b/hardeneks/cluster_wide/security/pod_security.py @@ -22,6 +22,7 @@ def ensure_namespace_psa_exist(resources: Resources): print_namespace_table( offenders, "[red]Namespaces should have psa modes.", + "Link: https://aws.github.io/aws-eks-best-practices/security/docs/pods/#pod-security-standards-pss-and-pod-security-admission-psa", ) return offenders diff --git a/hardeneks/namespace_based/reliability/applications.py b/hardeneks/namespace_based/reliability/applications.py index ccf2e58..482d4f4 100644 --- a/hardeneks/namespace_based/reliability/applications.py +++ b/hardeneks/namespace_based/reliability/applications.py @@ -17,7 +17,9 @@ def avoid_running_singleton_pods(namespaced_resources: NamespacedResources): if offenders: print_pod_table( - offenders, "[red]Avoid running pods without deployments." + offenders, + "[red]Avoid running pods without deployments.", + "Link: https://aws.github.io/aws-eks-best-practices/reliability/docs/application/#avoid-running-singleton-pods", ) return offenders @@ -31,7 +33,9 @@ def run_multiple_replicas(namespaced_resources: NamespacedResources): if offenders: print_deployment_table( - offenders, "[red]Avoid running single replica deployments" + offenders, + "[red]Avoid running single replica deployments", + "Link: https://aws.github.io/aws-eks-best-practices/reliability/docs/application/#run-multiple-replicas", ) return offenders @@ -52,7 +56,9 @@ def schedule_replicas_across_nodes(namespaced_resources: NamespacedResources): if offenders: print_service_table( - offenders, "[red]Spread replicas across AZs and Nodes" + offenders, + "[red]Spread replicas across AZs and Nodes", + "Link: https://aws.github.io/aws-eks-best-practices/reliability/docs/application/#schedule-replicas-across-nodes", ) return offenders @@ -70,7 +76,9 @@ def check_horizontal_pod_autoscaling_exists( if offenders: print_service_table( - offenders, "[red]Deploy horizontal pod autoscaler for deployments" + offenders, + "[red]Deploy horizontal pod autoscaler for deployments", + "Link: https://aws.github.io/aws-eks-best-practices/reliability/docs/application/#horizontal-pod-autoscaler-hpa", ) return offenders @@ -84,7 +92,11 @@ def check_readiness_probes(namespaced_resources: NamespacedResources): offenders.append(pod) if offenders: - print_pod_table(offenders, "[red]Define readiness probes for pods.") + print_pod_table( + offenders, + "[red]Define readiness probes for pods.", + "Link: https://aws.github.io/aws-eks-best-practices/reliability/docs/application/#use-readiness-probe-to-detect-partial-unavailability", + ) return offenders @@ -97,5 +109,9 @@ def check_liveness_probes(namespaced_resources: NamespacedResources): offenders.append(pod) if offenders: - print_pod_table(offenders, "[red]Define liveness probes for pods.") + print_pod_table( + offenders, + "[red]Define liveness probes for pods.", + "Link: https://aws.github.io/aws-eks-best-practices/reliability/docs/application/#use-liveness-probe-to-remove-unhealthy-pods", + ) return offenders diff --git a/hardeneks/namespace_based/security/encryption_secrets.py b/hardeneks/namespace_based/security/encryption_secrets.py index 4ec98fd..e174a7f 100644 --- a/hardeneks/namespace_based/security/encryption_secrets.py +++ b/hardeneks/namespace_based/security/encryption_secrets.py @@ -19,6 +19,10 @@ def disallow_secrets_from_env_vars(resources: NamespacedResources): offenders.append(pod) if offenders: - print_pod_table(offenders, "[red]Disallow secrets from env vars") + print_pod_table( + offenders, + "[red]Disallow secrets from env vars", + "Link: https://aws.github.io/aws-eks-best-practices/security/docs/data/#use-volume-mounts-instead-of-environment-variables", + ) return offenders diff --git a/hardeneks/namespace_based/security/iam.py b/hardeneks/namespace_based/security/iam.py index fd48aef..2bda99f 100644 --- a/hardeneks/namespace_based/security/iam.py +++ b/hardeneks/namespace_based/security/iam.py @@ -27,6 +27,7 @@ def restrict_wildcard_for_roles(resources: NamespacedResources): print_role_table( offenders, "[red]Roles should not have '*' in Verbs or Resources", + "Link: https://aws.github.io/aws-eks-best-practices/security/docs/iam/#employ-least-privileged-access-when-creating-rolebindings-and-clusterrolebindings", "Role", ) return offenders @@ -43,6 +44,7 @@ def disable_service_account_token_mounts(resources: NamespacedResources): print_pod_table( offenders, "[red]Auto-mounting of Service Account tokens is not allowed", + "Link: https://aws.github.io/aws-eks-best-practices/security/docs/iam/#disable-auto-mounting-of-service-account-tokens", ) return offenders @@ -59,7 +61,11 @@ def disable_run_as_root_user(resources: NamespacedResources): offenders.append(pod) if offenders: - print_pod_table(offenders, "[red]Running as root is not allowed") + print_pod_table( + offenders, + "[red]Running as root is not allowed", + "Link: https://aws.github.io/aws-eks-best-practices/security/docs/iam/#run-the-application-as-a-non-root-user", + ) return offenders @@ -80,6 +86,7 @@ def disable_anonymous_access_for_roles(resources: NamespacedResources): print_role_table( offenders, "[red]Don't bind roles to anonymous or unauthenticated groups", + "Link: https://aws.github.io/aws-eks-best-practices/security/docs/iam/#review-and-revoke-unnecessary-anonymous-access", "RoleBinding", ) return offenders @@ -109,6 +116,7 @@ def use_dedicated_service_accounts_for_each_deployment( print_workload_table( offenders, "[red]Don't share service accounts between Deployments", + "Link: https://aws.github.io/aws-eks-best-practices/security/docs/iam/#use-dedicated-service-accounts-for-each-application", "Deployment", ) @@ -139,6 +147,7 @@ def use_dedicated_service_accounts_for_each_stateful_set( print_workload_table( offenders, "[red]Don't share service accounts between StatefulSets", + "Link: https://aws.github.io/aws-eks-best-practices/security/docs/iam/#use-dedicated-service-accounts-for-each-application", "StatefulSet", ) @@ -169,6 +178,7 @@ def use_dedicated_service_accounts_for_each_daemon_set( print_workload_table( offenders, "[red]Don't share service accounts between DaemonSets", + "Link: https://aws.github.io/aws-eks-best-practices/security/docs/iam/#use-dedicated-service-accounts-for-each-application", "DaemonSet", ) diff --git a/hardeneks/namespace_based/security/network_security.py b/hardeneks/namespace_based/security/network_security.py index fd4e721..8712fea 100644 --- a/hardeneks/namespace_based/security/network_security.py +++ b/hardeneks/namespace_based/security/network_security.py @@ -28,6 +28,8 @@ def use_encryption_with_aws_load_balancers( if offenders: print_service_table( - offenders, "[red]Make sure you specify an ssl cert" + offenders, + "[red]Make sure you specify an ssl cert", + "Link: https://aws.github.io/aws-eks-best-practices/security/docs/network/#use-encryption-with-aws-load-balancers", ) return offenders diff --git a/hardeneks/namespace_based/security/pod_security.py b/hardeneks/namespace_based/security/pod_security.py index d2a66c0..b75dd36 100644 --- a/hardeneks/namespace_based/security/pod_security.py +++ b/hardeneks/namespace_based/security/pod_security.py @@ -25,7 +25,9 @@ def disallow_container_socket_mount(namespaced_resources: NamespacedResources): if offenders: print_pod_table( - offenders, "[red]Container socket mounts are not allowed" + offenders, + "[red]Container socket mounts are not allowed", + "Link: https://aws.github.io/aws-eks-best-practices/security/docs/pods/#never-run-docker-in-docker-or-mount-the-socket-in-the-container", ) return offenders @@ -45,6 +47,7 @@ def disallow_host_path_or_make_it_read_only( print_pod_table( offenders, "[red]Restrict the use of hostpath.", + "Link: https://aws.github.io/aws-eks-best-practices/security/docs/pods/#restrict-the-use-of-hostpath-or-if-hostpath-is-necessary-restrict-which-prefixes-can-be-used-and-configure-the-volume-as-read-only", ) return offenders @@ -66,6 +69,7 @@ def set_requests_limits_for_containers( print_pod_table( offenders, "[red]Set requests and limits for each container.", + "Link: https://aws.github.io/aws-eks-best-practices/security/docs/pods/#set-requests-and-limits-for-each-container-to-avoid-resource-contention-and-dos-attacks", ) return offenders @@ -86,6 +90,7 @@ def disallow_privilege_escalation(namespaced_resources: NamespacedResources): print_pod_table( offenders, "[red]Set allowPrivilegeEscalation in the pod spec to false", + "Link: https://aws.github.io/aws-eks-best-practices/security/docs/pods/#do-not-allow-privileged-escalation", ) return offenders @@ -106,6 +111,7 @@ def check_read_only_root_file_system( print_pod_table( offenders, "[red]Configure your images with a read-only root file system", + "Link: https://aws.github.io/aws-eks-best-practices/security/docs/pods/#configure-your-images-with-read-only-root-file-system", ) return offenders diff --git a/hardeneks/namespace_based/security/runtime_security.py b/hardeneks/namespace_based/security/runtime_security.py index 54a8530..644482f 100644 --- a/hardeneks/namespace_based/security/runtime_security.py +++ b/hardeneks/namespace_based/security/runtime_security.py @@ -40,6 +40,7 @@ def disallow_linux_capabilities(namespaced_resources: NamespacedResources): """ [red]Capabilities beyond the allowed list are disallowed. """, + "Link: https://aws.github.io/aws-eks-best-practices/security/docs/runtime/#consider-adddropping-linux-capabilities-before-writing-seccomp-policies", ) return offenders diff --git a/hardeneks/report.py b/hardeneks/report.py index 86dcae5..79881c5 100644 --- a/hardeneks/report.py +++ b/hardeneks/report.py @@ -6,7 +6,7 @@ console = Console() -def print_role_table(roles, message, type): +def print_role_table(roles, message, docs, type): table = Table() table.add_column("Kind", style="cyan") @@ -16,11 +16,11 @@ def print_role_table(roles, message, type): for role in roles: table.add_row(type, role.metadata.namespace, role.metadata.name) - print(Panel(table, title=message)) + print(Panel(table, title=message, subtitle=docs)) console.print() -def print_instance_metadata_table(instances, message): +def print_instance_metadata_table(instances, message, docs): table = Table() table.add_column("InstanceId", style="cyan") @@ -36,11 +36,11 @@ def print_instance_metadata_table(instances, message): ), ) - print(Panel(table, title=message)) + print(Panel(table, title=message, subtitle=docs)) console.print() -def print_instance_public_table(instances, message): +def print_instance_public_table(instances, message, docs): table = Table() table.add_column("InstanceId", style="cyan") @@ -56,7 +56,7 @@ def print_instance_public_table(instances, message): console.print() -def print_repository_table(repositories, attribute, message): +def print_repository_table(repositories, attribute, message, docs): table = Table() table.add_column("Repository", style="cyan") table.add_column(attribute, style="magenta") @@ -66,11 +66,11 @@ def print_repository_table(repositories, attribute, message): repository[attribute], ) - print(Panel(table, title=message)) + print(Panel(table, title=message, subtitle=docs)) console.print() -def print_pod_table(pods, message): +def print_pod_table(pods, message, docs): table = Table() table.add_column("Kind", style="cyan") @@ -80,11 +80,11 @@ def print_pod_table(pods, message): for pod in pods: table.add_row("Pod", pod.metadata.namespace, pod.metadata.name) - print(Panel(table, title=message)) + print(Panel(table, title=message, subtitle=docs)) console.print() -def print_workload_table(workloads, message, kind): +def print_workload_table(workloads, message, docs, kind): table = Table() table.add_column("Kind", style="cyan") @@ -96,11 +96,11 @@ def print_workload_table(workloads, message, kind): kind, workload.metadata.namespace, workload.metadata.name ) - print(Panel(table, title=message)) + print(Panel(table, title=message, subtitle=docs)) console.print() -def print_namespace_table(namespaces, message): +def print_namespace_table(namespaces, message, docs): table = Table() table.add_column("Namespace", style="cyan") @@ -110,11 +110,11 @@ def print_namespace_table(namespaces, message): namespace, ) - print(Panel(table, title=message)) + print(Panel(table, title=message, subtitle=docs)) console.print() -def print_service_table(services, message): +def print_service_table(services, message, docs): table = Table() table.add_column("Kind", style="cyan") @@ -126,11 +126,11 @@ def print_service_table(services, message): "Service", workload.metadata.namespace, workload.metadata.name ) - print(Panel(table, title=message)) + print(Panel(table, title=message, subtitle=docs)) console.print() -def print_deployment_table(deployments, message): +def print_deployment_table(deployments, message, docs): table = Table() table.add_column("Kind", style="cyan") @@ -142,11 +142,11 @@ def print_deployment_table(deployments, message): "Deployment", workload.metadata.namespace, workload.metadata.name ) - print(Panel(table, title=message)) + print(Panel(table, title=message, subtitle=docs)) console.print() -def print_storage_class_table(storage_classes, message): +def print_storage_class_table(storage_classes, message, docs): table = Table() table.add_column("StorageClass", style="cyan") @@ -155,11 +155,11 @@ def print_storage_class_table(storage_classes, message): for storage_class in storage_classes: table.add_row(storage_class.metadata.name, "false") - print(Panel(table, title=message)) + print(Panel(table, title=message, subtitle=docs)) console.print() -def print_persistent_volume_table(persistent_volumes, message): +def print_persistent_volume_table(persistent_volumes, message, docs): table = Table() table.add_column("PersistentVolume", style="cyan") @@ -168,5 +168,5 @@ def print_persistent_volume_table(persistent_volumes, message): for persistent_volume in persistent_volumes: table.add_row(persistent_volume.metadata.name, "false") - print(Panel(table, title=message)) + print(Panel(table, title=message, subtitle=docs)) console.print()