Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 49 additions & 39 deletions cmd/tls-scanner/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package main
import (
"flag"
"fmt"
"log"
"log/slog"
"net"
"os"
"path/filepath"
Expand Down Expand Up @@ -70,11 +70,33 @@ func run(args []string) (exitCode int) {
pqcCheck := fs.Bool("pqc-check", false, "Quick check for TLS 1.3 and ML-KEM (post-quantum) support only")
timingFile := fs.String("timing-file", "", "Output timing report to specified file in artifact-dir")
showVersion := fs.Bool("version", false, "Print version and exit")
logLevel := fs.String("log-level", "info", "Log level: debug, info, warn, error")

if err := fs.Parse(args); err != nil {
return 2
}

var level slog.Level
if err := level.UnmarshalText([]byte(*logLevel)); err != nil {
fmt.Fprintf(os.Stderr, "Error: invalid log level %q (valid: debug, info, warn, error)\n", *logLevel)
return 2
}
logOutput := os.Stderr
if *logFile != "" {
f, err := os.OpenFile(*logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: opening log file: %v\n", err)
return 1
}
defer func() {
if cerr := f.Close(); cerr != nil {
slog.Warn("failed to close log file", "error", cerr)
}
}()
logOutput = f
}
slog.SetDefault(slog.New(slog.NewTextHandler(logOutput, &slog.HandlerOptions{Level: level})))

if *showVersion {
fmt.Printf("tls-scanner %s (commit: %s)\n", version, commit)
return 0
Expand All @@ -84,85 +106,73 @@ func run(args []string) (exitCode int) {

if *generateTemplate != "" {
if err := scanner.GenerateTemplate(*generateTemplate); err != nil {
log.Printf("Error writing template: %v", err)
slog.Error("writing template", "error", err)
return 1
}
fmt.Printf("Template written to %s\n", *generateTemplate)
return 0
}

policy := scanner.Policy()
policy, err := scanner.Policy()
if err != nil {
slog.Error("loading policy", "error", err)
return 1
}

defer func() {
if *timingFile != "" {
path := filepath.Join(*artifactDir, *timingFile)
if err := timing.Timings.WriteReport(path); err != nil {
log.Printf("Warning: Could not write timing report: %v", err)
slog.Warn("could not write timing report", "error", err)
} else {
log.Printf("Timing report written to %s", path)
slog.Info("timing report written", "path", path)
}
}
}()

if *logFile != "" {
f, err := os.OpenFile(*logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
log.Printf("error opening file: %v", err)
return 1
}
defer func() {
if cerr := f.Close(); cerr != nil {
log.Printf("Warning: failed to close log file: %v", cerr)
}
}()
log.SetOutput(f)
log.Printf("Logging to file: %s", *logFile)
}

if !scanner.IsTestSSLInstalled() {
log.Print("Error: testssl.sh is not installed or not in the system's PATH.")
slog.Error("testssl.sh is not installed or not in the system's PATH")
return 1
}

if *concurrentScans == 0 {
*concurrentScans = runtime.NumCPU()
log.Printf("Concurrency: %d (from CPU count)", *concurrentScans)
slog.Info("concurrency set from CPU count", "workers", *concurrentScans)
} else if *concurrentScans < 0 {
log.Print("Error: Number of concurrent scans must be >= 0 (0 = auto)")
slog.Error("number of concurrent scans must be >= 0 (0 = auto)")
return 1
}

var client *k8s.Client
var err error
var pods []k8s.PodInfo

if *targets != "" {
targetList := strings.Split(*targets, ",")
if len(targetList) == 0 || (len(targetList) == 1 && targetList[0] == "") {
log.Print("Error: --targets flag provided but no targets were specified")
slog.Error("--targets flag provided but no targets were specified")
return 1
}

var jobs []scanner.ScanJob
for _, t := range targetList {
hostValue, portValue, err := parseTarget(t)
if err != nil {
log.Printf("Warning: Skipping invalid target format: %s (expected host:port)", t)
slog.Warn("skipping invalid target format", "target", t, "expected", "host:port")
continue
}
jobs = append(jobs, scanner.ScanJob{IP: hostValue, Port: portValue})
}

if len(jobs) == 0 {
log.Print("Error: No valid targets found in --targets flag")
slog.Error("no valid targets found in --targets flag")
return 1
}

scanResults := scanner.Scan(jobs, *concurrentScans, nil, nil, policy)
finalScanResults = &scanResults

if err := output.WriteOutputFiles(scanResults, *artifactDir, *jsonFile, *csvFile, *junitFile, isPQCCheck); err != nil {
log.Printf("Error writing output files: %v", err)
slog.Error("writing output files", "error", err)
return 1
}
if isPQCCheck {
Expand All @@ -177,15 +187,15 @@ func run(args []string) (exitCode int) {
if *templateFile != "" {
jobs, err := scanner.LoadTemplate(*templateFile)
if err != nil {
log.Printf("Error loading template: %v", err)
slog.Error("loading template", "error", err)
return 1
}

scanResults := scanner.Scan(jobs, *concurrentScans, nil, nil, policy)
finalScanResults = &scanResults

if err := output.WriteOutputFiles(scanResults, *artifactDir, *jsonFile, *csvFile, *junitFile, isPQCCheck); err != nil {
log.Printf("Error writing output files: %v", err)
slog.Error("writing output files", "error", err)
return 1
}
if isPQCCheck {
Expand All @@ -200,24 +210,24 @@ func run(args []string) (exitCode int) {
if *allPods {
client, err = k8s.NewClient()
if err != nil {
log.Printf("Could not create kubernetes client for --all-pods: %v", err)
slog.Error("could not create kubernetes client for --all-pods", "error", err)
return 1
}

pods, err = client.GetAllPodsInfo()
if err != nil {
log.Printf("Error listing pods: %v", err)
slog.Error("listing pods", "error", err)
return 1
}
pods = client.FilterPodsByComponent(pods, *componentFilter)
pods = k8s.FilterPodsByNamespace(pods, *namespaceFilter)

if len(pods) == 0 {
log.Print("Warning: no pods found matching the given filters, nothing to scan")
slog.Warn("no pods found matching the given filters, nothing to scan")
return 0
}

log.Printf("Found %d pods to scan from the cluster.", len(pods))
slog.Info("pods to scan", "count", len(pods))

if *limitIPs > 0 {
totalIPs := 0
Expand All @@ -226,13 +236,13 @@ func run(args []string) (exitCode int) {
}

if totalIPs > *limitIPs {
log.Printf("Limiting scan to %d IPs (found %d total IPs)", *limitIPs, totalIPs)
slog.Info("limiting scan", "limit", *limitIPs, "found", totalIPs)
pods = scanner.LimitPodsToIPCount(pods, *limitIPs)
limitedTotal := 0
for _, pod := range pods {
limitedTotal += len(pod.IPs)
}
log.Printf("After limiting: %d pods with %d total IPs", len(pods), limitedTotal)
slog.Info("after limiting", "pods", len(pods), "ips", limitedTotal)
}
}
}
Expand All @@ -242,7 +252,7 @@ func run(args []string) (exitCode int) {
finalScanResults = &scanResults

if err := output.WriteOutputFiles(scanResults, *artifactDir, *jsonFile, *csvFile, *junitFile, isPQCCheck); err != nil {
log.Printf("Error writing output files: %v", err)
slog.Error("writing output files", "error", err)
return 1
}
if isPQCCheck {
Expand All @@ -256,7 +266,7 @@ func run(args []string) (exitCode int) {

portNum, err := strconv.Atoi(*port)
if err != nil {
log.Printf("Invalid port: %s", *port)
slog.Error("invalid port", "port", *port)
return 1
}

Expand All @@ -265,7 +275,7 @@ func run(args []string) (exitCode int) {
finalScanResults = &scanResults

if err := output.WriteOutputFiles(scanResults, *artifactDir, *jsonFile, *csvFile, *junitFile, isPQCCheck); err != nil {
log.Printf("Error writing output files: %v", err)
slog.Error("writing output files", "error", err)
return 1
}
if isPQCCheck {
Expand Down
24 changes: 12 additions & 12 deletions internal/k8s/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package k8s
import (
"context"
"fmt"
"log"
"log/slog"
"os"
"strings"

Expand All @@ -20,13 +20,13 @@ import (
func NewClient() (*Client, error) {
config, err := rest.InClusterConfig()
if err != nil {
log.Printf("Could not load in-cluster config, falling back to kubeconfig: %v", err)
slog.Warn("could not load in-cluster config, falling back to kubeconfig", "error", err)
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
config, err = clientcmd.BuildConfigFromFlags("", loadingRules.GetDefaultFilename())
if err != nil {
return nil, fmt.Errorf("could not get kubernetes config: %v", err)
}
log.Println("Successfully created Kubernetes client from kubeconfig file")
slog.Info("Successfully created Kubernetes client from kubeconfig file")
}

clientset, err := kubernetes.NewForConfig(config)
Expand Down Expand Up @@ -75,7 +75,7 @@ func NewClient() (*Client, error) {
}

func (c *Client) GetAllPodsInfo() ([]PodInfo, error) {
log.Println("Getting all pods from the cluster...")
slog.Info("Getting all pods from the cluster...")
pods, err := c.clientset.CoreV1().Pods("").List(context.TODO(), metav1.ListOptions{})
if err != nil {
return nil, fmt.Errorf("could not list pods: %w", err)
Expand All @@ -84,7 +84,7 @@ func (c *Client) GetAllPodsInfo() ([]PodInfo, error) {
var allPodsInfo []PodInfo
for _, pod := range pods.Items {
if pod.Status.PodIP == "" {
log.Printf("Skipping pod %s/%s: no IP address assigned (phase: %s)", pod.Namespace, pod.Name, pod.Status.Phase)
slog.Debug("skipping pod with no IP address", "namespace", pod.Namespace, "pod", pod.Name, "phase", pod.Status.Phase)
continue
}

Expand All @@ -108,7 +108,7 @@ func (c *Client) GetAllPodsInfo() ([]PodInfo, error) {
}
allPodsInfo = append(allPodsInfo, podInfo)
}
log.Printf("Found %d pods in the cluster (with IP addresses)", len(allPodsInfo))
slog.Info("pods found in cluster", "count", len(allPodsInfo))

totalIPs := 0
uniqueIPs := make(map[string]bool)
Expand All @@ -118,7 +118,7 @@ func (c *Client) GetAllPodsInfo() ([]PodInfo, error) {
uniqueIPs[ip] = true
}
}
log.Printf("IP discovery summary: %d total IPs across %d pods (%d unique IPs).", totalIPs, len(allPodsInfo), len(uniqueIPs))
slog.Info("IP discovery summary", "totalIPs", totalIPs, "pods", len(allPodsInfo), "uniqueIPs", len(uniqueIPs))

return allPodsInfo, nil
}
Expand All @@ -128,7 +128,7 @@ func (c *Client) FilterPodsByComponent(pods []PodInfo, componentFilter string) [
return pods
}

log.Printf("Filtering pods by component name(s): %s", componentFilter)
slog.Info("filtering pods by component", "filter", componentFilter)
filterComponents := strings.Split(componentFilter, ",")
filterSet := make(map[string]struct{})
for _, comp := range filterComponents {
Expand All @@ -139,14 +139,14 @@ func (c *Client) FilterPodsByComponent(pods []PodInfo, componentFilter string) [
for _, pod := range pods {
component, err := c.GetOpenshiftComponentFromImage(pod.Image)
if err != nil {
log.Printf("Warning: could not get component for image %s: %v", pod.Image, err)
slog.Warn("could not get component for image", "image", pod.Image, "error", err)
continue
}
if _, ok := filterSet[component.Component]; ok {
filtered = append(filtered, pod)
}
}
log.Printf("Filtered pods: %d remaining out of %d", len(filtered), len(pods))
slog.Info("filtered pods by component", "remaining", len(filtered), "total", len(pods))
return filtered
}

Expand All @@ -155,7 +155,7 @@ func FilterPodsByNamespace(pods []PodInfo, namespaceFilter string) []PodInfo {
return pods
}

log.Printf("Filtering pods by namespace(s): %s", namespaceFilter)
slog.Info("filtering pods by namespace", "filter", namespaceFilter)
filterNamespaces := strings.Split(namespaceFilter, ",")
filterSet := make(map[string]struct{})
for _, ns := range filterNamespaces {
Expand All @@ -168,6 +168,6 @@ func FilterPodsByNamespace(pods []PodInfo, namespaceFilter string) []PodInfo {
filtered = append(filtered, pod)
}
}
log.Printf("Filtered pods by namespace: %d remaining out of %d", len(filtered), len(pods))
slog.Info("filtered pods by namespace", "remaining", len(filtered), "total", len(pods))
return filtered
}
10 changes: 5 additions & 5 deletions internal/k8s/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,23 @@ package k8s
import (
"context"
"fmt"
"log"
"log/slog"
"strings"

v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func (c *Client) GetOpenshiftComponentFromImage(image string) (*OpenshiftComponent, error) {
log.Printf("Analyzing OpenShift image: %s", image)
slog.Debug("analyzing openshift image", "image", image)

component := c.parseOpenshiftComponentFromImageRef(image)
if component != nil {
log.Printf("Successfully parsed component info from image: %s -> %s", image, component.Component)
slog.Debug("parsed component info from image", "image", image, "component", component.Component)
return component, nil
}

log.Printf("Attempting to gather component info from cluster metadata for: %s", image)
slog.Debug("gathering component info from cluster metadata", "image", image)
return c.getComponentFromClusterMetadata(image)
}

Expand Down Expand Up @@ -69,7 +69,7 @@ func (c *Client) parseOpenshiftComponentFromImageRef(image string) *OpenshiftCom
}

func (c *Client) getComponentFromClusterMetadata(image string) (*OpenshiftComponent, error) {
log.Printf("Searching cluster for pods using image: %s", image)
slog.Debug("searching cluster for pods using image", "image", image)

pods, err := c.clientset.CoreV1().Pods("").List(context.Background(), metav1.ListOptions{})
if err != nil {
Expand Down
Loading