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
8 changes: 5 additions & 3 deletions cmd/tls-scanner/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ func run(args []string) (exitCode int) {

isPQCCheck = *pqcCheck

policy := scanner.Policy()

defer func() {
if *timingFile != "" {
path := filepath.Join(*artifactDir, *timingFile)
Expand Down Expand Up @@ -145,7 +147,7 @@ func run(args []string) (exitCode int) {
return 1
}

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

if err := output.WriteOutputFiles(scanResults, *artifactDir, *jsonFile, *csvFile, *junitFile, isPQCCheck); err != nil {
Expand Down Expand Up @@ -202,7 +204,7 @@ func run(args []string) (exitCode int) {
}

if len(pods) > 0 {
scanResults := scanner.PerformClusterScan(pods, *concurrentScans, client)
scanResults := scanner.PerformClusterScan(pods, *concurrentScans, client, policy)
finalScanResults = &scanResults

if err := output.WriteOutputFiles(scanResults, *artifactDir, *jsonFile, *csvFile, *junitFile, isPQCCheck); err != nil {
Expand All @@ -225,7 +227,7 @@ func run(args []string) (exitCode int) {
}

jobs := []scanner.ScanJob{{IP: normalizeHost(*host), Port: portNum}}
scanResults := scanner.Scan(jobs, *concurrentScans, client, nil)
scanResults := scanner.Scan(jobs, *concurrentScans, client, nil, policy)
finalScanResults = &scanResults

if err := output.WriteOutputFiles(scanResults, *artifactDir, *jsonFile, *csvFile, *junitFile, isPQCCheck); err != nil {
Expand Down
41 changes: 29 additions & 12 deletions internal/k8s/tls.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,21 @@ func (c *Client) GetTLSSecurityProfile() (*TLSSecurityProfile, error) {

profile := &TLSSecurityProfile{}

if ingressTLS, err := c.getIngressControllerTLS(); err != nil {
log.Printf("Warning: Could not get Ingress Controller TLS config: %v", err)
} else {
profile.IngressController = ingressTLS
}

// APIServer is fetched first — it is the cluster-wide default that Ingress and
// Kubelet inherit when no component-specific override is configured.
if apiServerTLS, err := c.getAPIServerTLS(); err != nil {
log.Printf("Warning: Could not get API Server TLS config: %v", err)
} else {
profile.APIServer = apiServerTLS
}

if kubeletTLS, err := c.getKubeletTLS(); err != nil {
if ingressTLS, err := c.getIngressControllerTLS(profile.APIServer); err != nil {
log.Printf("Warning: Could not get Ingress Controller TLS config: %v", err)
} else {
profile.IngressController = ingressTLS
}

if kubeletTLS, err := c.getKubeletTLS(profile.APIServer); err != nil {
log.Printf("Warning: Could not get Kubelet TLS config: %v", err)
} else {
profile.KubeletConfig = kubeletTLS
Expand All @@ -42,7 +44,7 @@ func (c *Client) GetTLSSecurityProfile() (*TLSSecurityProfile, error) {
return profile, nil
}

func (c *Client) getIngressControllerTLS() (*IngressTLSProfile, error) {
func (c *Client) getIngressControllerTLS(fallback *APIServerTLSProfile) (*IngressTLSProfile, error) {
ingress, err := c.operatorClient.OperatorV1().IngressControllers("openshift-ingress-operator").Get(context.Background(), "default", metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("failed to get IngressController custom resource: %v", err)
Expand All @@ -51,9 +53,16 @@ func (c *Client) getIngressControllerTLS() (*IngressTLSProfile, error) {
profile := &IngressTLSProfile{}

if ingress.Spec.TLSSecurityProfile == nil {
profile.Type = defaultProfileName
profile.Ciphers = defaultProfileCiphers
profile.MinTLSVersion = defaultProfileMinVer
// No explicit override: inherit the cluster-wide APIServer profile.
if fallback != nil {
profile.Type = fallback.Type
profile.Ciphers = fallback.Ciphers
profile.MinTLSVersion = fallback.MinTLSVersion
} else {
profile.Type = defaultProfileName
profile.Ciphers = defaultProfileCiphers
profile.MinTLSVersion = defaultProfileMinVer
}
return profile, nil
}

Expand Down Expand Up @@ -115,7 +124,7 @@ func (c *Client) getAPIServerTLS() (*APIServerTLSProfile, error) {
return profile, nil
}

func (c *Client) getKubeletTLS() (*KubeletTLSProfile, error) {
func (c *Client) getKubeletTLS(fallback *APIServerTLSProfile) (*KubeletTLSProfile, error) {
kubeletConfigs, err := c.mcfgClient.MachineconfigurationV1().KubeletConfigs().List(context.Background(), metav1.ListOptions{})
if err != nil {
return nil, fmt.Errorf("failed to list KubeletConfigs: %v", err)
Expand All @@ -141,5 +150,13 @@ func (c *Client) getKubeletTLS() (*KubeletTLSProfile, error) {
}
}

// No explicit KubeletConfig override: inherit the cluster-wide APIServer profile.
if fallback != nil {
return &KubeletTLSProfile{
TLSCipherSuites: fallback.Ciphers,
MinTLSVersion: fallback.MinTLSVersion,
}, nil
}

return nil, fmt.Errorf("no KubeletConfig with a TLSSecurityProfile found in the cluster")
}
6 changes: 3 additions & 3 deletions internal/output/junit.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,13 @@ func WriteJUnitOutput(scanResults scanner.ScanResults, filename string, pqcCheck
}

var failures []string
if portResult.IngressTLSConfigCompliance != nil && (!portResult.IngressTLSConfigCompliance.Version || !portResult.IngressTLSConfigCompliance.Ciphers) {
if portResult.IngressTLSConfigCompliance != nil && !scanner.IsTLSConfigCompliant(portResult.IngressTLSConfigCompliance) {
failures = append(failures, "Ingress TLS config is not compliant.")
}
if portResult.APIServerTLSConfigCompliance != nil && (!portResult.APIServerTLSConfigCompliance.Version || !portResult.APIServerTLSConfigCompliance.Ciphers) {
if portResult.APIServerTLSConfigCompliance != nil && !scanner.IsTLSConfigCompliant(portResult.APIServerTLSConfigCompliance) {
failures = append(failures, "API Server TLS config is not compliant.")
}
if portResult.KubeletTLSConfigCompliance != nil && (!portResult.KubeletTLSConfigCompliance.Version || !portResult.KubeletTLSConfigCompliance.Ciphers) {
if portResult.KubeletTLSConfigCompliance != nil && !scanner.IsTLSConfigCompliant(portResult.KubeletTLSConfigCompliance) {
failures = append(failures, "Kubelet TLS config is not compliant.")
}

Expand Down
79 changes: 52 additions & 27 deletions internal/scanner/compliance.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,22 @@ import (
"github.com/openshift/tls-scanner/internal/k8s"
)

// ComponentType identifies which TLS profile should be used when checking
// compliance for a scanned port.
type ComponentType int

const (
// GenericComponent covers all components that have no override capability —
// they must honor the cluster-wide APIServer TLS profile.
GenericComponent ComponentType = iota
// IngressComponent covers ports whose effective TLS profile is the
// IngressController profile (with APIServer fallback when no override is set).
IngressComponent
// KubeletComponent covers ports whose effective TLS profile is the
// KubeletConfig profile (with APIServer fallback when no override is set).
KubeletComponent
)

func getMinVersionValue(versions []string) int {
if len(versions) == 0 {
return 0
Expand All @@ -19,10 +35,10 @@ func getMinVersionValue(versions []string) int {
}

type profileInput struct {
profileType string
minTLSVersion string
profileType string
minTLSVersion string
expectedCiphers []string
result *TLSConfigComplianceResult
result *TLSConfigComplianceResult
}

func evaluateCompliance(scannedMinVer int, scannedCiphers []string, input profileInput) {
Expand All @@ -35,31 +51,43 @@ func evaluateCompliance(scannedMinVer int, scannedCiphers []string, input profil
input.result.Ciphers = checkCipherCompliance(scannedCiphers, input.expectedCiphers)
}

func CheckCompliance(portResult *PortResult, tlsProfile *k8s.TLSSecurityProfile) {
// CheckCompliance evaluates whether the port's observed TLS configuration
// honours the profile that applies to its component type:
// - IngressComponent → IngressController profile (or APIServer if no override)
// - KubeletComponent → KubeletConfig profile (or APIServer if no override)
// - GenericComponent → APIServer profile
//
// Only the relevant TLSConfigComplianceResult field on portResult is populated,
// leaving the others nil so callers never need to reason about which one to check.
func CheckCompliance(portResult *PortResult, tlsProfile *k8s.TLSSecurityProfile, componentType ComponentType) {
scannedMinVer := 0
if portResult.TlsVersions != nil {
scannedMinVer = getMinVersionValue(portResult.TlsVersions)
}

portResult.IngressTLSConfigCompliance = &TLSConfigComplianceResult{}
portResult.APIServerTLSConfigCompliance = &TLSConfigComplianceResult{}
portResult.KubeletTLSConfigCompliance = &TLSConfigComplianceResult{}

var profiles []profileInput

if ing := tlsProfile.IngressController; ing != nil {
profiles = append(profiles, profileInput{ing.Type, ing.MinTLSVersion, ing.Ciphers, portResult.IngressTLSConfigCompliance})
}
if api := tlsProfile.APIServer; api != nil {
profiles = append(profiles, profileInput{api.Type, api.MinTLSVersion, api.Ciphers, portResult.APIServerTLSConfigCompliance})
}
if kube := tlsProfile.KubeletConfig; kube != nil {
profiles = append(profiles, profileInput{"", kube.MinTLSVersion, kube.TLSCipherSuites, portResult.KubeletTLSConfigCompliance})
switch componentType {
case IngressComponent:
if ing := tlsProfile.IngressController; ing != nil {
portResult.IngressTLSConfigCompliance = &TLSConfigComplianceResult{}
evaluateCompliance(scannedMinVer, portResult.TlsCiphers, profileInput{ing.Type, ing.MinTLSVersion, ing.Ciphers, portResult.IngressTLSConfigCompliance})
}
case KubeletComponent:
if kube := tlsProfile.KubeletConfig; kube != nil {
portResult.KubeletTLSConfigCompliance = &TLSConfigComplianceResult{}
evaluateCompliance(scannedMinVer, portResult.TlsCiphers, profileInput{"", kube.MinTLSVersion, kube.TLSCipherSuites, portResult.KubeletTLSConfigCompliance})
}
default:
if api := tlsProfile.APIServer; api != nil {
portResult.APIServerTLSConfigCompliance = &TLSConfigComplianceResult{}
evaluateCompliance(scannedMinVer, portResult.TlsCiphers, profileInput{api.Type, api.MinTLSVersion, api.Ciphers, portResult.APIServerTLSConfigCompliance})
}
Comment thread
smith-xyz marked this conversation as resolved.
}
}

for _, p := range profiles {
evaluateCompliance(scannedMinVer, portResult.TlsCiphers, p)
}
// IsTLSConfigCompliant returns true when a compliance result exists and both
// the version and cipher checks passed.
func IsTLSConfigCompliant(result *TLSConfigComplianceResult) bool {
return result != nil && result.Version && result.Ciphers
}

func checkCipherCompliance(gotCiphers []string, expectedCiphers []string) bool {
Expand Down Expand Up @@ -89,16 +117,13 @@ func checkCipherCompliance(gotCiphers []string, expectedCiphers []string) bool {
func HasComplianceFailures(results ScanResults) bool {
for _, ipResult := range results.IPResults {
for _, portResult := range ipResult.PortResults {
Comment thread
smith-xyz marked this conversation as resolved.
if portResult.IngressTLSConfigCompliance != nil &&
(!portResult.IngressTLSConfigCompliance.Version || !portResult.IngressTLSConfigCompliance.Ciphers) {
if portResult.IngressTLSConfigCompliance != nil && !IsTLSConfigCompliant(portResult.IngressTLSConfigCompliance) {
return true
}
if portResult.APIServerTLSConfigCompliance != nil &&
(!portResult.APIServerTLSConfigCompliance.Version || !portResult.APIServerTLSConfigCompliance.Ciphers) {
if portResult.APIServerTLSConfigCompliance != nil && !IsTLSConfigCompliant(portResult.APIServerTLSConfigCompliance) {
return true
}
if portResult.KubeletTLSConfigCompliance != nil &&
(!portResult.KubeletTLSConfigCompliance.Version || !portResult.KubeletTLSConfigCompliance.Ciphers) {
if portResult.KubeletTLSConfigCompliance != nil && !IsTLSConfigCompliant(portResult.KubeletTLSConfigCompliance) {
return true
}
}
Expand Down
Loading