Skip to content

Commit

Permalink
fix ContainerImageDigest in events
Browse files Browse the repository at this point in the history
Signed-off-by: Matthias Bertschy <[email protected]>
  • Loading branch information
matthyx committed Nov 21, 2024
1 parent 4fb52bf commit 2c62707
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 51 deletions.
6 changes: 5 additions & 1 deletion pkg/containerwatcher/v1/container_watcher_private.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ func (ch *IGContainerWatcher) containerCallback(notif containercollection.PubSub

switch notif.Type {
case containercollection.EventTypeAddContainer:
logger.L().Info("start monitor on container", helpers.String("container ID", notif.Container.Runtime.ContainerID), helpers.String("k8s workload", k8sContainerID))
logger.L().Info("start monitor on container",
helpers.String("container ID", notif.Container.Runtime.ContainerID),
helpers.String("k8s workload", k8sContainerID),
helpers.String("ContainerImageDigest", notif.Container.Runtime.ContainerImageDigest),
helpers.String("ContainerImageName", notif.Container.Runtime.ContainerImageName))
if ch.running {
ch.timeBasedContainers.Add(notif.Container.Runtime.ContainerID)
} else {
Expand Down
109 changes: 59 additions & 50 deletions pkg/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"crypto/sha1"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"hash"
Expand All @@ -17,7 +18,6 @@ import (
"slices"
"strconv"
"strings"
"syscall"
"time"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -46,6 +46,7 @@ import (
igtypes "github.com/inspektor-gadget/inspektor-gadget/pkg/types"

apitypes "github.com/armosec/armoapi-go/armotypes"
kubeletconfigv1beta1 "k8s.io/kubelet/config/v1beta1"
)

var (
Expand Down Expand Up @@ -475,9 +476,9 @@ func GetFileSize(path string) (int64, error) {
}

func CalculateSHA256FileExecHash(path string, args []string) string {
hash := sha256.New()
hash.Write([]byte(fmt.Sprintf("%s;%v", path, args)))
hashInBytes := hash.Sum(nil)
hsh := sha256.New()
hsh.Write([]byte(fmt.Sprintf("%s;%v", path, args)))
hashInBytes := hsh.Sum(nil)
return hex.EncodeToString(hashInBytes)
}

Expand Down Expand Up @@ -683,49 +684,6 @@ func ChunkBy[T any](items []T, chunkSize int) [][]T {
}

// isUnixSocket checks if the given path is a Unix socket.
func isUnixSocket(path string) (bool, error) {
fileInfo, err := os.Stat(path)
if err != nil {
return false, err // Could not obtain the file stats
}

stat, ok := fileInfo.Sys().(*syscall.Stat_t)
if !ok {
return false, fmt.Errorf("not a unix file")
}

// Check if the file is a socket
return (stat.Mode & syscall.S_IFMT) == syscall.S_IFSOCK, nil
}

func DetectContainerRuntimes(hostMount string) ([]*containerutilsTypes.RuntimeConfig, error) {
runtimes := map[igtypes.RuntimeName]string{
igtypes.RuntimeNameDocker: runtimeclient.DockerDefaultSocketPath,
igtypes.RuntimeNameCrio: runtimeclient.CrioDefaultSocketPath,
igtypes.RuntimeNameContainerd: runtimeclient.ContainerdDefaultSocketPath,
igtypes.RuntimeNamePodman: runtimeclient.PodmanDefaultSocketPath,
}

detectedRuntimes := make([]*containerutilsTypes.RuntimeConfig, 0)

for runtimeName, socketPath := range runtimes {
// Check if the socket is available on the host mount
socketPath = hostMount + socketPath
if isSocket, err := isUnixSocket(socketPath); err == nil && isSocket {
logger.L().Info("Detected container runtime", helpers.String("runtime", runtimeName.String()), helpers.String("socketPath", socketPath))
detectedRuntimes = append(detectedRuntimes, &containerutilsTypes.RuntimeConfig{
Name: runtimeName,
SocketPath: socketPath,
})
}
}

if len(detectedRuntimes) == 0 {
return nil, fmt.Errorf("no container runtimes detected at the following paths: %v", runtimes)
}

return detectedRuntimes, nil
}

func DetectContainerRuntimeViaK8sAPI(ctx context.Context, k8sClient *k8sinterface.KubernetesApi, nodeName string) (*containerutilsTypes.RuntimeConfig, error) {
// Get the current node
Expand All @@ -735,17 +693,23 @@ func DetectContainerRuntimeViaK8sAPI(ctx context.Context, k8sClient *k8sinterfac
if err != nil {
return nil, fmt.Errorf("failed to list nodes: %v", err)
}

if len(nodes.Items) == 0 {
return nil, fmt.Errorf("no node found with name: %s", nodeName)
}

node := nodes.Items[0]
// parse the runtime info
runtimeConfig := parseRuntimeInfo(node.Status.NodeInfo.ContainerRuntimeVersion)
if runtimeConfig.Name == igtypes.RuntimeNameUnknown {
return nil, fmt.Errorf("unknown container runtime: %s", node.Status.NodeInfo.ContainerRuntimeVersion)
}

// override the socket path
realSocketPath, err := getContainerRuntimeSocketPath(k8sClient, nodeName)
if err != nil {
return nil, fmt.Errorf("failed to get container runtime socket path from Kubelet configz: %v", err)
}
runtimeConfig.SocketPath = realSocketPath
// unset the runtime protocol
runtimeConfig.RuntimeProtocol = ""
return runtimeConfig, nil
}

Expand Down Expand Up @@ -783,3 +747,48 @@ func parseRuntimeInfo(version string) *containerutilsTypes.RuntimeConfig {
}
}
}

func getContainerRuntimeSocketPath(clientset *k8sinterface.KubernetesApi, nodeName string) (string, error) {
kubeletConfig, err := getCurrentKubeletConfig(clientset, nodeName)
if err != nil {
return "", fmt.Errorf("getting /configz: %w", err)
}
socketPath, found := strings.CutPrefix(kubeletConfig.ContainerRuntimeEndpoint, "unix://")
if !found {
return "", fmt.Errorf("socket path does not start with unix://")
}
logger.L().Info("using the detected container runtime socket path from Kubelet's config", helpers.String("socketPath", socketPath))
return socketPath, nil
}

// The /configz endpoint isn't officially documented. It was introduced in Kubernetes 1.26 and been around for a long time
// as stated in https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/component-base/configz/OWNERS
func getCurrentKubeletConfig(clientset *k8sinterface.KubernetesApi, nodeName string) (*kubeletconfigv1beta1.KubeletConfiguration, error) {
resp, err := clientset.GetKubernetesClient().CoreV1().RESTClient().Get().Resource("nodes").
Name(nodeName).Suffix("proxy", "configz").DoRaw(context.TODO())
if err != nil {
return nil, fmt.Errorf("fetching /configz from %q: %w", nodeName, err)
}
kubeCfg, err := decodeConfigz(resp)
if err != nil {
return nil, err
}
return kubeCfg, nil
}

// Decodes the http response from /configz and returns the kubelet configuration
func decodeConfigz(respBody []byte) (*kubeletconfigv1beta1.KubeletConfiguration, error) {
// This hack because /configz reports the following structure:
// {"kubeletconfig": {the JSON representation of kubeletconfigv1beta1.KubeletConfiguration}}
type configzWrapper struct {
ComponentConfig kubeletconfigv1beta1.KubeletConfiguration `json:"kubeletconfig"`
}

configz := configzWrapper{}
err := json.Unmarshal(respBody, &configz)
if err != nil {
return nil, err
}

return &configz.ComponentConfig, nil
}

0 comments on commit 2c62707

Please sign in to comment.