Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add rule type change detection in profile parse #401

Merged
merged 12 commits into from
Dec 1, 2023
110 changes: 3 additions & 107 deletions cmd/manager/scap.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,7 @@ import (

mcfgv1 "github.com/openshift/machine-config-operator/pkg/apis/machineconfiguration.openshift.io/v1"
mcfgcommon "github.com/openshift/machine-config-operator/pkg/controller/common"
"github.com/wI2L/jsondiff"
"gopkg.in/yaml.v3"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
runtimejson "k8s.io/apimachinery/pkg/runtime/serializer/json"
runtimeclient "sigs.k8s.io/controller-runtime/pkg/client"
Expand All @@ -48,10 +46,8 @@ import (
)

const (
contentFileTimeout = 3600
valuePrefix = "xccdf_org.ssgproject.content_value_"
kubeletConfigPathPrefix = "/kubeletconfig/"
kubeletConfigRolePathPrefix = "/kubeletconfig/role/"
contentFileTimeout = 3600
valuePrefix = "xccdf_org.ssgproject.content_value_"
)

var (
Expand Down Expand Up @@ -192,16 +188,6 @@ func (c *scapContentDataStream) FigureResources(profile string) error {
},
}

roleNodesList, err := fetchNodesWithRole(context.Background(), c.resourceFetcherClients.client)
if err != nil {
LOG("Failed to fetch role list with nodes, error: %v", err)
return err
}

if len(roleNodesList) > 0 {
found = append(found, getKubeletConfigResourcePath(roleNodesList)...)
}

effectiveProfile := profile
var valuesList map[string]string

Expand Down Expand Up @@ -245,56 +231,6 @@ func getPathFromWarningXML(in *xmlquery.Node, valueList map[string]string) []uti
return path
}

// Fetch all nodes from the cluster and find all roles for each node.
func fetchNodesWithRole(ctx context.Context, c runtimeclient.Client) (map[string][]string, error) {
nodeList := v1.NodeList{}
if err := c.List(ctx, &nodeList); err != nil {
return nil, fmt.Errorf("failed to list nodes: %w", err)
}

roleNodesList := make(map[string][]string)
for _, node := range nodeList.Items {
nodeName := node.Name
nodeRoles := utils.GetNodeRoles(node.ObjectMeta.Labels)
for _, role := range nodeRoles {
roleNodesList[role] = append(roleNodesList[role], nodeName)
}
}

return roleNodesList, nil

}

// Get resourcePath for KubeletConfig
func getKubeletConfigResourcePath(roleNodesList map[string][]string) []utils.ResourcePath {
resourcePath := []utils.ResourcePath{}
for role, nodeList := range roleNodesList {
for _, node := range nodeList {
resourcePath = append(resourcePath, utils.ResourcePath{
ObjPath: "/api/v1/nodes/" + node + "/proxy/configz",
DumpPath: kubeletConfigPathPrefix + role + "/" + node,
Filter: `.kubeletconfig|.kind="KubeletConfiguration"|.apiVersion="kubelet.config.k8s.io/v1beta1"`,
})
}
}
return resourcePath
}

// Get role name and node name from DumpPath
func getRoleNodeNameFromDumpPath(dumpPath string) (roleName string, nodeName string) {
if strings.HasPrefix(dumpPath, kubeletConfigPathPrefix) {
dumpPathSplit := strings.Split(dumpPath, "/")
if len(dumpPathSplit) != 4 {
return "", ""
}
// example of DumpPath: /kubeletconfig/master/node-1
roleName = dumpPathSplit[2]
nodeName = dumpPathSplit[3]
return roleName, nodeName
}
return "", ""
}

// Collect the resource paths for objects that this scan needs to obtain.
// The profile will have a series of "selected" checks that we grab all of the path info from.
func getResourcePaths(profileDefs *xmlquery.Node, ruleDefs *xmlquery.Node, profile string, overrideValueList map[string]string) ([]utils.ResourcePath, map[string]string) {
Expand Down Expand Up @@ -597,47 +533,7 @@ func fetch(ctx context.Context, streamDispatcher streamerDispatcherFn, rfClients
return nil, warnings, err
}
}
results, warnings, err := saveConsistentKubeletResult(results, warnings)
return results, warnings, err
}

// Only save consistent KubeletConfigs per node role.
func saveConsistentKubeletResult(result map[string][]byte, warning []string) (map[string][]byte, []string, error) {
if len(result) == 0 {
return result, warning, nil
}
kubeletConfigsRole := make(map[string][]byte)
for dumpPath, content := range result {
role, node := getRoleNodeNameFromDumpPath(dumpPath)
if role == "" {
continue
}
if existingKC, ok := kubeletConfigsRole[role]; ok {
diff, err := jsondiff.CompareJSON(existingKC, content)
if err != nil {
return nil, nil, fmt.Errorf("couldn't compare kubelet configs: %w for %s", err, node)
}
if diff != nil {
why := fmt.Sprintf("Kubelet configs for %s are not consistent with role %s, Diff: %s of KubeletConfigs for %s role will not be saved.", node, role, diff, role)
LOG(why)
warning = append(warning, why)
intersectionKC, err := utils.JSONIntersection(existingKC, content)
if err != nil {
return nil, nil, fmt.Errorf("couldn't get intersection of kubelet configs: %w for %s", err, node)
}
kubeletConfigsRole[role] = intersectionKC
}
} else {
kubeletConfigsRole[role] = content
}
}
for role, content := range kubeletConfigsRole {
if role == "" {
continue
}
result[kubeletConfigRolePathPrefix+role] = content
}
return result, warning, nil
return results, warnings, nil
}

func filter(ctx context.Context, rawobj []byte, filter string) ([]byte, error) {
Expand Down
Loading