diff --git a/Dockerfile b/Dockerfile index 004f052..374c118 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,8 +10,11 @@ LABEL name="k8tls" \ description="Tool to scan/verify the TLS connection parameters and the certificates usage on the target server ports. The tool does not inject a proxy/sidecar to do this scanning." RUN microdnf -y update && \ - microdnf -y install --nodocs --setopt=install_weak_deps=0 --setopt=keepcache=0 shadow-utils openssl ca-certificates nmap jq tar gzip util-linux && \ - microdnf clean all + microdnf -y install --nodocs --setopt=install_weak_deps=0 --setopt=keepcache=0 shadow-utils openssl ca-certificates nmap jq tar unzip gzip util-linux && \ + microdnf clean all && \ + curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && \ + unzip awscliv2.zip && \ + ./aws/install # Determine architecture and download the appropriate binaries RUN ARCH=$(uname -m) && \ diff --git a/k8s/job.yaml b/k8s/job.yaml index 65b08bc..b80766c 100755 --- a/k8s/job.yaml +++ b/k8s/job.yaml @@ -14,9 +14,9 @@ kind: ClusterRole metadata: name: k8tls-cr rules: -- apiGroups: [""] - resources: ["services"] - verbs: ["get", "list"] + - apiGroups: [ "" ] + resources: [ "services" ] + verbs: [ "get", "list" ] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding @@ -27,9 +27,9 @@ roleRef: kind: ClusterRole name: k8tls-cr subjects: -- kind: ServiceAccount - name: k8tls-serviceact - namespace: k8tls + - kind: ServiceAccount + name: k8tls-serviceact + namespace: k8tls --- apiVersion: v1 kind: ConfigMap @@ -131,6 +131,16 @@ data: ] } --- +apiVersion: v1 +kind: Secret +metadata: + name: aws-credentials + namespace: k8tls +data: + credentials: | + + +--- apiVersion: batch/v1 kind: Job metadata: @@ -141,17 +151,29 @@ spec: spec: serviceAccountName: k8tls-serviceact containers: - - name: k8tls - image: kubearmor/k8tls:latest - command: ["./k8s_tlsscan"] - volumeMounts: - - mountPath: /home/k8tls/config/ - name: config - readOnly: true + - name: k8tls + image: kubearmor/k8tls:latest + imagePullPolicy: Always + command: + - "./main_k8s_tlsscan" + securityContext: + runAsUser: 0 + volumeMounts: + - mountPath: /home/k8tls/config/ + name: config + readOnly: true + + - mountPath: /home/k8tls/.aws/credentials + subPath: credentials + name: aws-credentials + readOnly: true restartPolicy: Never volumes: - name: config configMap: name: k8tls-cm + - name: aws-credentials + secret: + secretName: aws-credentials + defaultMode: 0400 backoffLimit: 4 ---- diff --git a/src/findings_tls b/src/findings_tls index c14f7b6..8c0be3a 100644 --- a/src/findings_tls +++ b/src/findings_tls @@ -1,5 +1,7 @@ #!/bin/bash +set -o pipefail + opensslscan() { tmp=/tmp/tls.out @@ -24,7 +26,7 @@ tls_csvreport() { [[ "$csvout" == "" ]] && return cat << EOF >> $csvout -"$SVC_Name","$SVC_Address","$TLS_Status","$TLS_Protocol_version","$TLS_Ciphersuite","$TLS_Hash_used","$TLS_Signature_type","$TLS_Verification","$FIPS_140_3_Compliant" +"$SVC_Name","$SVC_Address","$SVC_Type","$TLS_Status","$TLS_Protocol_version","$TLS_Ciphersuite","$TLS_Hash_used","$TLS_Signature_type","$TLS_Verification","$FIPS_140_3_Compliant" EOF } @@ -192,6 +194,53 @@ fips_compliance_check() appendSpec } +k8tls_tls_03exposedPorts() { + if [ "$SVC_Type" == "NodePort" ]; then + status="PASS" + clusterName=$(curl -s http://169.254.169.254/latest/user-data | grep -E '^/etc/eks/bootstrap\.sh\s+([^\s]+)' | cut -d' ' -f2 2> /dev/null) + securityGroups=$(curl -s http://169.254.169.254/latest/meta-data/security-groups 2> /dev/null) + MAC=$(curl -s http://169.254.169.254/latest/meta-data/network/interfaces/macs/ | head -n 1 2> /dev/null) + securityGroupId=$(curl -s http://169.254.169.254/latest/meta-data/network/interfaces/macs/${MAC}/security-group-ids 2> /dev/null) + vpcId=$(curl -s http://169.254.169.254/latest/meta-data/network/interfaces/macs/${MAC}/vpc-id 2> /dev/null) + subNet=$(curl -s http://169.254.169.254/latest/meta-data/network/interfaces/macs/${MAC}/subnet-id 2> /dev/null) + + if [[ -z "$clusterName" || -z "$securityGroups" || -z "$MAC" || -z "$securityGroupId" || -z "$vpcId" || -z "$subNet" ]]; then + echo "One or more fields are empty. Skipping exposedPorts check..." + return + # exit 1 + fi + + # Use parameter expansion to split the SVC_Name + namespace="${SVC_Name%%/*}" + name="${SVC_Name##*/}" + + nodePortValue=$(kubectl -n "${namespace}" get svc "${name}" -o=custom-columns='NODEPORT:.spec.ports[*].nodePort' | tail -1) + isExposedPort=$(aws ec2 describe-security-group-rules | jq ".SecurityGroupRules[] | select(.IsEgress == false and .ToPort == $nodePortValue)") + if [[ -z "${isExposedPort}" ]]; then + return + fi + + cat << EOF >> $TMPJSONSEC + { + "plugin": "exposed-ports", + "title": "Publicly exposed ports", + "description": "Publicly exposed ports increase security risks. Disable unnecessary ports to protect systems from unauthorized access." + "severity": "critical", + "remediationEstEffort": "medium", + "solution": "Disable publicly exposed ports", + "status": "$status", + "clusterName": "$clusterName", + "securityGroups": "$securityGroups", + "securityGroupId": "$securityGroupId", + "vpcId": "$vpcId", + "subNet": "$subNet", + "serviceNamespaceAndName": "$SVC_Name", + "exposedPort": "$nodePortValue" + } +EOF + fi +} + appendSpec() { cat << EOF >> $TMPJSONSEC diff --git a/src/k8s_tlsscan b/src/k8s_tlsscan index 341b0ca..835efc5 100755 --- a/src/k8s_tlsscan +++ b/src/k8s_tlsscan @@ -24,11 +24,12 @@ while read -r line; do IFS=' ' list=($(echo $line)) ns=${list[0]} svc=${list[1]} - clusterip=${list[2]} + svcType=${list[2]} + clusterip=${list[3]} ! [[ $clusterip =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] && continue - IFS=',' pnames=($(echo "${list[3]}")) - IFS=',' ports=($(echo "${list[4]}")) - IFS=',' prots=($(echo "${list[5]}")) + IFS=',' pnames=($(echo "${list[4]}")) + IFS=',' ports=($(echo "${list[5]}")) + IFS=',' prots=($(echo "${list[6]}")) for ((i=0;i<5;i++)); do pname="${pnames[$i]}" tport="${ports[$i]}" @@ -37,9 +38,9 @@ while read -r line; do [[ "$prot" != "TCP" ]] && echo "unsupported protocol $prot" && continue [[ "$pname" == "" ]] && pname="" [[ "$pname" != "" ]] && pname="[$pname]" - echo "$clusterip:$tport $ns/$svc$pname" >> $ADDRLIST + echo "$clusterip:$tport $ns/$svc$pname $svcType" >> $ADDRLIST done IFS=' ' -done < <(kubectl get svc --no-headers -A -o=custom-columns='NS:.metadata.namespace,NAME:.metadata.name,ClusterIP:.spec.clusterIP,PORTNAME:.spec.ports[*].name,PORT:.spec.ports[*].port,PROTOCOL:.spec.ports[*].protocol,TGTPORT:.spec.ports[*].targetPort') +done < <(kubectl get svc --no-headers -A -o=custom-columns='NS:.metadata.namespace,NAME:.metadata.name,TYPE:.spec.type,ClusterIP:.spec.clusterIP,PORTNAME:.spec.ports[*].name,PORT:.spec.ports[*].port,PROTOCOL:.spec.ports[*].protocol,TGTPORT:.spec.ports[*].targetPort') $BDIR/tlsscan --infile $ADDRLIST --compact-json diff --git a/src/main_k8s_tlsscan b/src/main_k8s_tlsscan new file mode 100755 index 0000000..14c585f --- /dev/null +++ b/src/main_k8s_tlsscan @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +aws configure set aws_access_key_id "$(head -1 /home/k8tls/.aws/credentials)" && \ +aws configure set aws_secret_access_key "$(tail -1 /home/k8tls/.aws/credentials)" + +./k8s_tlsscan && \ +printf "\nJSON Report: \n" && \ +cat /tmp/report.json diff --git a/src/tlsscan b/src/tlsscan index d97551b..5eaead6 100755 --- a/src/tlsscan +++ b/src/tlsscan @@ -60,7 +60,7 @@ csvheader() { [[ "$csvout" == "" ]] && return if [ ! -f "$csvout" ]; then - echo "Name,Address,Status,Version,Ciphersuite,Hash,Signature,Verification,FIPS_140_3_Compliant" > $csvout + echo "Name,Address,Type,Status,Version,Ciphersuite,Hash,Signature,Verification,FIPS_140_3_Compliant" > $csvout fi } @@ -87,6 +87,7 @@ jsonendpoint_hdr() "svc": "$SVC_Name", "host": "$SVC_Host", "port": "$SVC_Port", + "type": "$SVC_Type", "finding": [ EOF } @@ -187,7 +188,10 @@ main() SVC_Host=${SVC_Address/:*} SVC_Port=${SVC_Address/*:} SVC_Name=${arr[1]} - SVC_Scanners=${arr[2]} + SVC_Type=${arr[2]} + # Service type is only applicable for Kubernetes + [[ "$SVC_Type" == "" ]] && SVC_Type="NA" + SVC_Scanners=${arr[3]} [[ "$SVC_Scanners" == "" ]] && SVC_Scanners="tls" scansvc ((endpoint_cnt++))