代码拉取完成,页面将自动刷新
package specs
import (
"bufio"
"context"
"errors"
"fmt"
"gitee.com/anesec/chameleon/apis"
"gitee.com/anesec/chameleon/pkg/errdefs"
"gitee.com/anesec/chameleon/pkg/nsenter"
"gitee.com/anesec/chameleon/pkg/system"
"gitee.com/anesec/chameleon/pkg/system/procfs"
"gitee.com/anesec/chameleon/pkg/types"
"gitee.com/anesec/chameleon/pkg/utils"
ctypes "gitee.com/anesec/containers/types"
"github.com/hashicorp/golang-lru/v2/expirable"
"github.com/tklauser/go-sysconf"
"strconv"
"syscall"
//rtc "gitee.com/anesec/containers/apis/client"
rtc "gitee.com/anesec/containers"
"gitee.com/anesec/mobius/goroutine"
log "github.com/sirupsen/logrus"
"os"
"path/filepath"
"regexp"
"strings"
"sync"
"time"
)
var (
ClockTicks float64 = 100 // default value
singleton cache
)
type Each interface {
Containers(fn func(*Container))
Pods(fn func(*Pod))
Processes(fn func(*Process))
}
func init() {
clkTck, err := sysconf.Sysconf(sysconf.SC_CLK_TCK)
// ignore errors
if err == nil {
ClockTicks = float64(clkTck)
}
singleton.containers = make(map[string]*Container)
singleton.mount2containers = make(map[uint32]*Container)
singleton.processes = make(map[int]*Process)
singleton.users = expirable.NewLRU[uint32, map[string]*User](256, nil, time.Minute*10)
singleton.groups = expirable.NewLRU[uint32, map[string]*Group](256, nil, time.Minute*10)
}
func Load(ctx context.Context) error {
return singleton.load(ctx)
}
func GetHostMountNsID() uint32 {
return singleton.hostMountNsId
}
func GetContainer(id string) (*Container, error) {
return singleton.getContainer(id)
}
func GetContainerByMountID(id uint32) (*Container, error) {
return singleton.getContainerByMountID(id)
}
func GetContainerIDs() []string {
return singleton.getContainerIDs()
}
func GetPod(uid string) (*Pod, error) {
return singleton.getPod(uid)
}
func ForEach() Each {
return &singleton
}
func GetProcess(pid int) (*Process, error) {
return singleton.getProcess(pid)
}
func GetAttackGraph(pid int) []*apis.ProcessSpec {
return singleton.getAttackGraph(pid)
}
func ProcessClone(process *Process) {
singleton.processClone(process)
}
func ProcessExit(pid int) {
singleton.processExit(pid)
}
func GetProcessIDs() []int {
return singleton.getProcessIDs()
}
func GetAncestors(pid int) []int {
return singleton.getAncestors(pid)
}
func CgroupCreate(path string) (*Pod, *Container) {
return singleton.cgroupCreate(path)
}
func CgroupDelete(path string) (*Pod, *Container) {
return singleton.cgroupDelete(path)
}
func LookupUser(uid int) *User {
return singleton.lookupUserByPid(uid, 1)
}
func LookupUserByPid(uid, pid int) *User {
return singleton.lookupUserByPid(uid, pid)
}
func LookupUserByContainer(uid int, containerId string) *User {
return singleton.lookupUserByContainer(uid, containerId)
}
func LookupGroup(uid int) *Group {
return singleton.lookupGroupByPid(uid, 1)
}
func LookupGroupByPid(uid, pid int) *Group {
return singleton.lookupGroupByPid(uid, pid)
}
func LookupGroupByContainer(uid int, containerId string) *Group {
return singleton.lookupGroupByContainer(uid, containerId)
}
type cache struct {
pods map[string]*Pod
containers map[string]*Container
mount2containers map[uint32]*Container
processes map[int]*Process
mount2processes map[uint32]*Process
users *expirable.LRU[uint32, map[string]*User]
groups *expirable.LRU[uint32, map[string]*Group]
hostMountNsId uint32
cl sync.RWMutex
pl sync.RWMutex
}
func (c *cache) Containers(fn func(*Container)) {
c.cl.RLock()
for _, container := range c.containers {
fn(container)
}
c.cl.RUnlock()
}
func (c *cache) Pods(fn func(*Pod)) {
c.cl.RLock()
for _, pod := range c.pods {
fn(pod)
}
c.cl.RUnlock()
}
func (c *cache) Processes(fn func(*Process)) {
c.cl.RLock()
for _, process := range c.processes {
fn(process)
}
c.cl.RUnlock()
}
func (c *cache) load(ctx context.Context) (err error) {
hostProcfs := os.Getenv(types.ChameleonHostProcfs)
if c.hostMountNsId, err = nsenter.MountNsId(nsenter.Procfs(hostProcfs), nsenter.Pid(1)); err != nil {
return err
}
c.loadProcesses(ctx)
c.loadContainers(ctx)
c.loadPods(ctx)
goroutine.Run(ctx, func(ctx context.Context) {
ticker := time.NewTicker(time.Minute * 5)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
c.loadProcesses(ctx)
c.loadContainers(ctx)
c.loadPods(ctx)
}
}
})
return
}
func (c *cache) loadProcesses(ctx context.Context) {
processes, err := procfs.AllProcesses()
if err != nil {
log.Warnf("Unable to load all processes, %v", err)
return
}
pids := make(map[int]struct{}, len(processes))
for _, proc := range processes {
pids[proc.Pid()] = struct{}{}
c.pl.RLock()
process, ok := c.processes[proc.Pid()]
c.pl.RUnlock()
if !ok || process == nil {
process = &Process{ProcessSpec: &apis.ProcessSpec{Pid: int32(proc.Pid())}}
}
_ = c.enrichProcess(ctx, process)
}
// delete obsoleted processes
c.pl.Lock()
for pid := range c.processes {
if _, ok := pids[pid]; !ok {
delete(c.processes, pid)
}
}
c.pl.Unlock()
}
func (c *cache) loadPods(ctx context.Context) {
pods, err := rtc.PodList(ctx, &ctypes.PodListOptions{})
if err != nil {
log.Warnf("Load all pods failed, %v", err)
return
}
ids := make(map[string]struct{}, len(pods))
for _, p := range pods {
ids[p.ID] = struct{}{}
c.cl.RLock()
pod, ok := c.pods[p.ID]
c.cl.RUnlock()
if !ok || pod == nil {
pod = &Pod{PodSpec: &apis.PodSpec{Id: p.ID, Name: p.Name, Namespace: p.Namespace}}
}
_ = c.enrichPod(ctx, pod)
}
// delete obsoleted pods
c.cl.Lock()
for id := range c.pods {
if _, ok := ids[id]; !ok {
delete(c.pods, id)
}
}
c.cl.Unlock()
}
func (c *cache) loadContainers(ctx context.Context) {
containers, err := rtc.ContainerList(ctx, &ctypes.ContainerListOptions{})
if err != nil {
log.Warnf("Load all containers failed, %v", err)
return
}
ids := make(map[string]struct{}, len(containers))
for _, cont := range containers {
ids[cont.ID] = struct{}{}
c.cl.RLock()
container, ok := c.containers[cont.ID]
c.cl.RUnlock()
if !ok || container == nil {
container = &Container{ContainerSpec: &apis.ContainerSpec{Id: cont.ID}}
}
_ = c.enrichContainer(ctx, container)
}
// delete obsoleted pods
c.cl.Lock()
for id, container := range c.containers {
if _, ok := ids[id]; !ok {
delete(c.containers, id)
if container != nil {
delete(c.mount2containers, container.MountNsID)
}
}
}
c.cl.Unlock()
}
func (c *cache) getContainerIDs() (ids []string) {
c.cl.RLock()
ids = make([]string, 0, len(c.containers))
for id := range c.containers {
ids = append(ids, id)
}
c.cl.RUnlock()
return
}
func (c *cache) getContainer(id string) (*Container, error) {
c.cl.RLock()
container, ok := c.containers[id]
c.cl.RUnlock()
if !ok || container == nil {
container = &Container{ContainerSpec: &apis.ContainerSpec{Id: id}}
}
if err := c.enrichContainer(context.TODO(), container); err != nil {
return nil, err
}
return container, nil
}
func (c *cache) getContainerByMountID(id uint32) (*Container, error) {
c.cl.RLock()
container, ok := c.mount2containers[id]
c.cl.RUnlock()
if !ok || container == nil {
return nil, fmt.Errorf("container(mnt.ns=%d) %w", id, errdefs.ErrNotFound)
}
return container, nil
}
func (c *cache) getPod(uid string) (*Pod, error) {
c.cl.RLock()
pod, ok := c.pods[uid]
c.cl.RUnlock()
if !ok || pod == nil {
return nil, fmt.Errorf("pod(uid=%s) %w", uid, errdefs.ErrNotFound)
}
if err := c.enrichPod(context.TODO(), pod); err != nil {
return nil, err
}
return pod, nil
}
func (c *cache) getProcess(pid int) (*Process, error) {
c.pl.RLock()
process, ok := c.processes[pid]
c.pl.RUnlock()
if !ok || process == nil {
process = &Process{ProcessSpec: &apis.ProcessSpec{Pid: int32(pid)}}
}
if err := c.enrichProcess(context.TODO(), process); err != nil {
return nil, err
}
return process, nil
}
func (c *cache) getAttackGraph(pid int) []*apis.ProcessSpec {
return nil
}
func (c *cache) getProcessIDs() (pids []int) {
c.pl.RLock()
pids = make([]int, 0, len(c.processes))
for pid := range c.processes {
pids = append(pids, int(pid))
}
c.pl.RUnlock()
return
}
func (c *cache) getAncestors(pid int) (ancestors []int) {
c.pl.RLock()
process, ok := c.processes[pid]
if ok && process != nil {
ancestors = make([]int, 0, 8)
mountNsId := process.MountNsID
for {
if process, ok = c.processes[int(process.Ppid)]; ok && process != nil && process.MountNsID == mountNsId {
ancestors = append(ancestors, int(process.Pid))
continue
}
break
}
}
c.pl.RUnlock()
return
}
func (c *cache) processClone(process *Process) {
if process == nil || process.ProcessSpec == nil || process.Pid == 0 {
return
}
c.pl.Lock()
if _, ok := c.processes[int(process.Pid)]; !ok {
c.processes[int(process.Pid)] = process
goroutine.Run(context.TODO(), func(ctx context.Context) {
time.Sleep(time.Millisecond * 50)
c.pl.RLock()
if process, ok = c.processes[int(process.Pid)]; !ok || process == nil {
c.pl.RUnlock()
return
}
c.pl.RUnlock()
_ = c.enrichProcess(ctx, process)
})
}
c.pl.Unlock()
}
func (c *cache) processExit(pid int) {
c.pl.Lock()
delete(c.processes, pid)
c.pl.Unlock()
}
var (
containerIdMatcher = regexp.MustCompile("([0-9a-f]{64})")
podIdMatcher = regexp.MustCompile(`kubepods-.*-pod([0-9a-f_-]{36})`)
)
func (c *cache) cgroupCreate(path string) (pod *Pod, container *Container) {
var (
containerId string
podId string
ok bool
)
if containerId, podId = c.parseRuntimeIdFromCgroupPath(path); containerId == "" {
return
}
c.cl.Lock()
if podId != "" {
if pod, ok = c.pods[podId]; !ok {
// create pod sandbox container
pod = &Pod{PodSpec: &apis.PodSpec{Id: podId, Sandbox: containerId}}
c.pods[podId] = pod
c.cl.Unlock()
goroutine.Run(context.TODO(), func(ctx context.Context) {})
return
}
}
if container, ok = c.containers[containerId]; ok {
c.cl.Unlock()
return
}
container = &Container{ContainerSpec: &apis.ContainerSpec{Id: containerId}}
if pod != nil {
container.Pod = pod.PodSpec
}
c.containers[containerId] = container
goroutine.Run(context.TODO(), func(ctx context.Context) {
_ = c.enrichContainer(ctx, container)
})
c.cl.Unlock()
return
}
func (c *cache) cgroupDelete(path string) (pod *Pod, container *Container) {
var (
containerId string
podId string
ok bool
)
if containerId, podId = c.parseRuntimeIdFromCgroupPath(path); containerId == "" {
return
}
c.cl.Lock()
if container, ok = c.containers[containerId]; ok {
delete(c.mount2containers, container.MountNsID)
delete(c.containers, containerId)
} else if pod, ok = c.pods[podId]; ok {
delete(c.pods, podId)
}
c.cl.Unlock()
return
}
func (c *cache) parseRuntimeIdFromCgroupPath(path string) (containerId string, podId string) {
var file string
clusterEnabled := strings.Contains(path, "kubepods")
path, file = filepath.Split(path)
for ; len(file) > 0; path, file = filepath.Split(path) {
// no possible container id contained in path
if len(path) < 40 {
path = ""
}
if len(path) > 0 && path[len(path)-1] == '/' {
path = path[:len(path)-1]
}
if len(file) < 40 {
continue
}
if containerId == "" {
if matches := containerIdMatcher.FindStringSubmatch(file); len(matches) == 2 {
containerId = matches[1]
if !clusterEnabled {
break
}
}
// must contains container id
continue
}
if matches := podIdMatcher.FindStringSubmatch(file); len(matches) == 2 {
podId = strings.Replace(matches[1], "_", "-", -1)
break
}
}
return
}
func (c *cache) enrichPod(ctx context.Context, pod *Pod) (err error) {
if pod == nil || pod.Sandbox == "" {
return
}
if pod.enriched.Load() == 1 {
return
}
c.cl.Lock()
defer c.cl.Unlock()
if pod.enriched.Load() == 1 {
return
}
ctx, cancel := context.WithTimeout(ctx, time.Second*10)
defer func() {
cancel()
if err != nil {
delete(c.pods, pod.Id)
}
}()
var enrich *ctypes.Pod
if enrich, err = rtc.PodInspect(ctx, pod.Sandbox); err != nil {
log.Warnf("Unable to inspect pod %s, %v", pod.Id, err)
return
}
pod.Name = enrich.Name
pod.Namespace = enrich.Namespace
pod.Image = &apis.ImageSpec{
Id: enrich.ImageID,
Name: enrich.Image,
}
c.pods[pod.Id] = pod
pod.enriched.Store(1)
return
}
func (c *cache) enrichContainer(ctx context.Context, container *Container) (err error) {
if container == nil || container.Id == "" {
return
}
if container.enriched.Load() == 1 {
return
}
c.cl.Lock()
defer c.cl.Unlock()
if container.enriched.Load() == 1 {
return
}
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer func() {
cancel()
if err != nil {
delete(c.containers, container.Id)
}
}()
var enrich *ctypes.Container
if enrich, err = rtc.ContainerInspect(ctx, container.Id); err != nil {
log.Warnf("Unable to inspect container %s, %v", container.Id, err)
return
}
container.Name = enrich.Name
container.Pid = enrich.Pid
container.Image = &apis.ImageSpec{
Id: enrich.ImageID,
Name: enrich.Image,
}
if enrich.PodId != "" {
container.Pod = &apis.PodSpec{
Id: enrich.PodId,
Name: enrich.PodName,
Namespace: enrich.PodNamespace,
}
}
if container.MountNsID, err = nsenter.MountNsId(nsenter.Procfs(procfs.MountPoint()), nsenter.Pid(int(container.Pid))); err != nil {
log.Warnf("Unable to get container(%s) mount namespace, %v", container.Name, err)
return
}
proc, err := procfs.NewProcess(int(enrich.Pid))
if err != nil {
log.Warnf("Unable to get container(%s) runc pid, %v", container.Name, err)
return
}
if container.RuncPid, err = proc.PPid(); err != nil {
log.Warnf("Unable to get container(%s) runc pid, %v", container.Name, err)
return
}
c.containers[container.Id] = container
c.mount2containers[container.MountNsID] = container
container.enriched.Store(1)
return nil
}
func (c *cache) enrichProcess(_ context.Context, process *Process) (err error) {
if process == nil || process.ProcessSpec == nil || process.Pid == 0 {
return
}
if process.enriched.Load() == 1 {
return
}
c.pl.Lock()
defer c.pl.Unlock()
if process.enriched.Load() == 1 {
return
}
var (
proc *procfs.Process
cmdline []string
)
defer func() {
if err != nil && errors.Is(err, os.ErrNotExist) {
delete(c.processes, int(process.Pid))
}
}()
if proc, err = procfs.NewProcess(int(process.Pid)); err != nil {
return
}
if process.Name, err = proc.Comm(); err != nil {
return
}
if process.Binary, err = proc.Executable(); err != nil {
return
}
var info os.FileInfo
if len(process.Binary) > 0 {
if info, err = os.Stat(filepath.Join(procfs.MountPoint(), strconv.Itoa(int(process.Pid)), "root", process.Binary)); err == nil {
process.Mode = int64(info.Mode().Perm())
process.ModifyTime = info.ModTime().UnixNano()
if sys, ok := info.Sys().(*syscall.Stat_t); ok {
process.AccessTime = time.Unix(sys.Atim.Sec, sys.Atim.Nsec).UnixNano()
} else {
process.AccessTime = process.ModifyTime
}
}
}
if cmdline, err = proc.Cmdline(); err != nil {
return
}
process.Cmdline = strings.Join(cmdline, " ")
if process.MountNsID == 0 {
if process.MountNsID, err = proc.MountNsId(); err != nil {
log.Warnf("Unable to retrieve process(%d) mount namespace id, %v", proc.Pid(), err)
}
}
if process.NetNsID == 0 {
if process.NetNsID, err = proc.NetNsId(); err != nil {
log.Warnf("Unable to retrieve process(%d) network namespace, %v", proc.Pid(), err)
}
}
var status *procfs.Status
if status, err = proc.Status(); err != nil {
return
}
if uptime := system.GetUptime(); uptime > 0 {
var stat *procfs.Stat
if stat, err = proc.Stat(); err == nil && stat.Starttime > 0 {
process.StartTime = time.Unix(0, (time.Now().Unix()-(uptime-int64(stat.Starttime)/int64(ClockTicks)))*1e9).UnixNano()
}
}
process.NsPid = int32(status.NsPid)
process.Uid = status.Uid
process.Gid = status.Gid
if process.Ppid == 0 {
process.Ppid = int32(status.PPid)
}
if process.NsPid == 0 {
process.NsPid = int32(status.NsPid)
}
if process.Uid == 0 {
process.User = "root"
} else if user := c.lookupUserByPid(process.Uid, int(process.Pid), process.MountNsID); user != nil {
process.User = user.Name
}
if process.Gid == 0 {
process.Group = "root"
} else if group := c.lookupGroupByPid(process.Gid, int(process.Pid), process.MountNsID); group != nil {
process.Group = group.Name
}
c.processes[int(process.Pid)] = process
process.enriched.Store(1)
return nil
}
func (c *cache) lookupUserByPid(uid, pid int, mountNsId ...uint32) *User {
var (
mount uint32
err error
)
userId := strconv.Itoa(uid)
if len(mountNsId) == 1 && mountNsId[0] > 0 {
mount = mountNsId[0]
} else if pid == 1 {
mount = c.hostMountNsId
} else if mount, err = nsenter.MountNsId(nsenter.Pid(pid)); err != nil {
return &User{Uid: userId, Name: userId}
}
users, ok := c.users.Get(mount)
if !ok {
if users, err = c.loadUsers(filepath.Join(procfs.MountPoint(), strconv.Itoa(pid), "root/etc/passwd")); err != nil {
return &User{Uid: userId, Name: userId}
}
c.users.Add(mount, users)
}
user, ok := users[userId]
if ok && user != nil {
return user
}
return &User{Uid: userId, Name: userId}
}
func (c *cache) lookupUserByContainer(uid int, containerId string) *User {
c.cl.RLock()
container, ok := c.containers[containerId]
c.cl.RUnlock()
if !ok || container.Pid == 0 || container.MountNsID == 0 {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if enrich, err := rtc.ContainerInspect(ctx, containerId); err == nil {
container = &Container{
ContainerSpec: &apis.ContainerSpec{Pid: enrich.Pid},
}
} else {
userId := strconv.Itoa(uid)
return &User{Uid: userId, Name: userId}
}
}
return c.lookupUserByPid(uid, int(container.Pid), container.MountNsID)
}
func (c *cache) lookupGroupByPid(gid, pid int, mountNsId ...uint32) *Group {
var (
mount uint32
err error
)
groupId := strconv.Itoa(gid)
if len(mountNsId) == 1 && mountNsId[0] > 0 {
mount = mountNsId[0]
} else if pid == 1 {
mount = c.hostMountNsId
} else if mount, err = nsenter.MountNsId(nsenter.Pid(pid), nsenter.Procfs(procfs.MountPoint())); err != nil {
return &Group{Gid: groupId, Name: groupId}
}
groups, ok := c.groups.Get(mount)
if !ok {
if groups, err = c.loadGroups(filepath.Join(procfs.MountPoint(), strconv.Itoa(pid), "root/etc/group")); err != nil {
return &Group{Gid: groupId, Name: groupId}
}
c.groups.Add(mount, groups)
}
group, ok := groups[groupId]
if ok && group != nil {
return group
}
return &Group{Gid: groupId, Name: groupId}
}
func (c *cache) lookupGroupByContainer(gid int, containerId string) *Group {
c.cl.RLock()
container, ok := c.containers[containerId]
c.cl.RUnlock()
if !ok || container.Pid == 0 || container.MountNsID == 0 {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if enrich, err := rtc.ContainerInspect(ctx, containerId); err == nil {
container = &Container{
ContainerSpec: &apis.ContainerSpec{Pid: enrich.Pid},
}
} else {
groupId := strconv.Itoa(gid)
return &Group{Gid: groupId, Name: groupId}
}
}
return c.lookupGroupByPid(gid, int(container.Pid), container.MountNsID)
}
func (c *cache) loadUsers(filename string) (map[string]*User, error) {
buf := utils.Acquire(utils.BufferSize4K)
defer utils.Recycle(buf)
if err := utils.BufferedRead(filename, buf); err != nil {
return nil, err
}
users := make(map[string]*User)
scanner := bufio.NewScanner(buf)
for scanner.Scan() {
line := scanner.Text()
fields := strings.Split(line, ":")
if len(fields) != 7 {
continue
}
users[fields[2]] = &User{
Name: fields[0],
Uid: fields[2],
Gid: fields[3],
Home: fields[5],
Shell: filepath.Base(fields[6]),
}
}
return users, nil
}
func (c *cache) loadGroups(filename string) (map[string]*Group, error) {
buf := utils.Acquire(utils.BufferSize4K)
defer utils.Recycle(buf)
if err := utils.BufferedRead(filename, buf); err != nil {
return nil, err
}
groups := make(map[string]*Group)
scanner := bufio.NewScanner(buf)
for scanner.Scan() {
line := scanner.Text()
fields := strings.Split(line, ":")
if len(fields) != 4 {
continue
}
groups[fields[2]] = &Group{
Name: fields[0],
Gid: fields[2],
Users: strings.Split(fields[3], ","),
}
}
return groups, nil
}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。