Distroless Container Support
This document outlines the modifications needed in the CINC Auditor train-k8s-container plugin to support scanning distroless containers using Kubernetes ephemeral containers.
What are Distroless Containers?
Distroless containers are minimal container images that don't contain a shell or package managers, only the application and its runtime dependencies. This reduces the attack surface but makes traditional scanning more difficult.
Current Plugin Limitations
The current train-k8s-container plugin has these limitations with distroless containers:
- Relies on shell execution (not available in distroless containers)
- Cannot execute commands directly in the container
- Has no fallback mechanism for containers without shells
Required Modifications
1. Distroless Detection
Add capability to detect distroless containers by attempting to execute a simple shell command and checking for failure:
| # in lib/train/k8s/container/connection.rb
def distroless?(namespace, pod, container)
cmd = ["kubectl", "exec", "-n", namespace, pod, "-c", container, "--", "/bin/sh", "-c", "echo test"]
begin
result = Train::Extras::CommandWrapper.run(cmd.join(" "), nil)
return false # Container has shell
rescue Train::Errors::CommandExecutionError
return true # Container is likely distroless
end
end
|
2. Ephemeral Container Creation
Add functionality to create and connect to an ephemeral container:
| # in lib/train/k8s/container/connection.rb
def setup_ephemeral_container(namespace, pod, target_container)
debug_container_name = "inspec-debug-#{SecureRandom.hex(4)}"
debug_image = "alpine:latest" # or a custom image with needed tools
# Create ephemeral container
cmd = [
"kubectl", "debug", pod,
"-n", namespace,
"--image=#{debug_image}",
"--target=#{target_container}",
"--container=#{debug_container_name}",
"--quiet", "-it", "--", "sleep", "3600"
]
# Run in background
pid = Process.spawn(cmd.join(" "), [:out, :err] => "/dev/null")
Process.detach(pid)
# Wait for ephemeral container to be ready
sleep 5
# Return ephemeral container info
{
name: debug_container_name,
pid: pid
}
end
|
3. Connection Strategy Switching
Modify the connection logic to choose between standard and ephemeral container approaches:
| # in lib/train/k8s/container/connection.rb
def initialize(options)
@options = options
@namespace = options[:namespace]
@pod = options[:pod]
@container = options[:container]
# Detect if container is distroless
if distroless?(@namespace, @pod, @container)
@ephemeral = setup_ephemeral_container(@namespace, @pod, @container)
@container = @ephemeral[:name] # Use ephemeral container for commands
@using_ephemeral = true
else
@using_ephemeral = false
end
# Initialize kubernetes client
@k8s_client = KubectlExecClient.new(
namespace: @namespace,
pod: @pod,
container: @container,
kubeconfig: @options[:kubeconfig]
)
end
def close
# Clean up ephemeral container if used
if @using_ephemeral && @ephemeral[:pid]
Process.kill('TERM', @ephemeral[:pid])
end
end
|
4. File Access for Distroless Containers
Modify file access methods to work through the ephemeral container:
| # in lib/train/k8s/container/connection.rb
def file(path)
if @using_ephemeral
# For distroless containers, access target container filesystem via /proc
# First, get the process ID of the target container's entrypoint
target_pid_cmd = "ps -ef | grep #{@options[:container]} | grep -v grep | awk '{print $2}' | head -1"
target_pid = @k8s_client.run_command(target_pid_cmd).stdout.strip
# Access target container's filesystem via /proc
modified_path = "/proc/#{target_pid}/root#{path}"
Train::File::Local.new(self, modified_path)
else
# Standard file access
Train::File::Remote.new(self, path)
end
end
|
5. Command Execution Handling
Update command execution to handle the distroless case:
| # in lib/train/k8s/container/kubectl_exec_client.rb
def run_command(command)
if @connection.using_ephemeral?
# In ephemeral container, we might need to modify commands to access the target container
# This depends on how exactly we want to interact with the target container
modified_command = command
super(modified_command)
else
# Standard command execution
super(command)
end
end
|
Implementation Strategy
- Fork the Repository: Create a fork of the train-k8s-container plugin
- Create Branch: Create a feature branch for distroless support
- Implement Changes: Make the modifications outlined above
- Add Tests: Create tests for distroless container detection and scanning
- Document: Document the new capabilities and how to use them
- Submit PR: Consider submitting a pull request to the upstream repository
Integration with Our Project
After modifying the plugin, we would need to:
- Update our Gemfile to point to our fork of the plugin
- Update our scan-container.sh script to handle the new capabilities
- Create documentation on how to scan distroless containers
- Update our Helm chart to include the new plugin version
- Test with various distroless container types
Example Usage
With the modified plugin, the command to scan a distroless container would remain the same:
| cinc-auditor exec my-profile -t k8s-container://namespace/pod/container
|
The plugin would automatically:
- Detect the container is distroless
- Create an ephemeral container
- Execute the scan through the ephemeral container
- Clean up the ephemeral container when done
Potential Limitations
- Permissions: Requires permissions to create ephemeral containers
- Kubernetes Version: Requires Kubernetes v1.18+ for ephemeral containers
- Image Compatibility: The debug image must have required tools
- Process Isolation: May have issues with certain container runtimes