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

Type Assertion Paranoia #121

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
91 changes: 48 additions & 43 deletions controllers/configurationpolicy_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -1140,11 +1140,11 @@ func (r *ConfigurationPolicyReconciler) handleObjectTemplates(plc policyv1.Confi
// violations for enforce configurationpolicies are already handled in handleObjects,
// so we only need to generate a violation if the remediationAction is set to inform
if !handled && !enforce {
objData := map[string]interface{}{
"indx": indx,
"kind": kind,
"desiredName": templateObjs[indx].name,
"namespaced": templateObjs[indx].isNamespaced,
objData := templateIdentifier{
index: indx,
kind: kind,
desiredName: templateObjs[indx].name,
namespaced: templateObjs[indx].isNamespaced,
}

statusUpdateNeeded := createInformStatus(
Expand Down Expand Up @@ -1335,6 +1335,13 @@ func addConditionToStatus(
return updateNeeded
}

type templateIdentifier struct {
desiredName string
index int
kind string
namespaced bool
}

// createInformStatus updates the status field for a configurationpolicy with remediationAction=inform
// based on how many compliant/noncompliant objects are found when processing the templates in the configurationpolicy
func createInformStatus(
Expand All @@ -1344,18 +1351,9 @@ func createInformStatus(
compliantObjects,
nonCompliantObjects map[string]map[string]interface{},
plc *policyv1.ConfigurationPolicy,
objData map[string]interface{},
objData templateIdentifier,
) bool {
//nolint:forcetypeassert
desiredName := objData["desiredName"].(string)
//nolint:forcetypeassert
indx := objData["indx"].(int)
//nolint:forcetypeassert
kind := objData["kind"].(string)
//nolint:forcetypeassert
namespaced := objData["namespaced"].(bool)

if kind == "" {
if objData.kind == "" {
return false
}

Expand All @@ -1374,7 +1372,7 @@ func createInformStatus(
compObjs = compliantObjects
}

return createStatus(desiredName, kind, compObjs, namespaced, plc, indx, compliant, objShouldExist)
return createStatus(objData, compObjs, plc, compliant, objShouldExist)
}

// handleObjects controls the processing of each individual object template within a configurationpolicy
Expand Down Expand Up @@ -1623,6 +1621,12 @@ func (r *ConfigurationPolicyReconciler) handleSingleObj(
},
}

objData := templateIdentifier{
kind: obj.gvr.Resource,
namespaced: obj.namespaced,
index: obj.index,
}

if !exists && obj.shouldExist {
// it is a musthave and it does not exist, so it must be created
if strings.EqualFold(string(remediation), string(policyv1.Enforce)) {
Expand All @@ -1638,8 +1642,7 @@ func (r *ConfigurationPolicyReconciler) handleSingleObj(
// object is missing and will be created, so send noncompliant "does not exist" event first
// (this check has already happened, but we send the event here to avoid the status flipping on an
// error)
_ = createStatus("", obj.gvr.Resource, compliantObject, obj.namespaced, obj.policy,
obj.index, false, true)
_ = createStatus(objData, compliantObject, obj.policy, false, true)
obj.policy.Status.ComplianceState = policyv1.NonCompliant
statusStr := convertPolicyStatusToString(obj.policy)
objLog.Info("Sending a noncompliant status event (object missing)", "policy", obj.policy.Name, "status",
Expand Down Expand Up @@ -1685,8 +1688,7 @@ func (r *ConfigurationPolicyReconciler) handleSingleObj(
if strings.EqualFold(string(remediation), string(policyv1.Enforce)) {
log.V(2).Info("Entering `does not exist` and `must not have`")

statusUpdateNeeded = createStatus("", obj.gvr.Resource, compliantObject, obj.namespaced, obj.policy,
obj.index, compliant, false)
statusUpdateNeeded = createStatus(objData, compliantObject, obj.policy, compliant, false)
}
}

Expand All @@ -1707,8 +1709,7 @@ func (r *ConfigurationPolicyReconciler) handleSingleObj(

if triedUpdate && !strings.Contains(msg, "Error validating the object") {
// object has a mismatch and needs an update to be enforced, throw violation for mismatch
_ = createStatus("", obj.gvr.Resource, compliantObject, obj.namespaced, obj.policy, obj.index,
false, true)
_ = createStatus(objData, compliantObject, obj.policy, false, true)
obj.policy.Status.ComplianceState = policyv1.NonCompliant
statusStr := convertPolicyStatusToString(obj.policy)
objLog.Info("Sending an update policy status event", "policy", obj.policy.Name, "status", statusStr)
Expand Down Expand Up @@ -1748,8 +1749,7 @@ func (r *ConfigurationPolicyReconciler) handleSingleObj(

statusUpdateNeeded = addConditionToStatus(obj.policy, obj.index, true, reason, msg)
} else {
statusUpdateNeeded = createStatus("", obj.gvr.Resource, compliantObject, obj.namespaced, obj.policy,
obj.index, compliant, true)
statusUpdateNeeded = createStatus(objData, compliantObject, obj.policy, compliant, true)
}
created := false
creationInfo = &policyv1.ObjectProperties{
Expand Down Expand Up @@ -1978,7 +1978,7 @@ func buildNameList(
}

if match {
kindNameList = append(kindNameList, uObj.Object["metadata"].(map[string]interface{})["name"].(string))
kindNameList = append(kindNameList, uObj.GetName())
}
}

Expand Down Expand Up @@ -2264,6 +2264,11 @@ func mergeSpecsHelper(templateVal, existingVal interface{}, ctype string) interf
return templateVal.(string)
}

type countedVal struct {
value interface{}
count int
}

// mergeArrays is a helper function that takes a list from the existing object and merges in all the data that is
// different in the template. This way, comparing the merged object to the one that exists on the cluster will tell
// you whether the existing object is compliant with the template
Expand All @@ -2280,18 +2285,15 @@ func mergeArrays(newArr []interface{}, old []interface{}, ctype string) (result
}

// create a set with a key for each unique item in the list
oldItemSet := map[string]map[string]interface{}{}
oldItemSet := make(map[string]*countedVal)

for _, val2 := range old {
key := fmt.Sprint(val2)

if entry, ok := oldItemSet[key]; ok {
oldItemSet[key]["count"] = entry["count"].(int) + 1
entry.count++
} else {
oldItemSet[key] = map[string]interface{}{
"count": 1,
"value": val2,
}
oldItemSet[key] = &countedVal{value: val2, count: 1}
}
}

Expand All @@ -2306,10 +2308,8 @@ func mergeArrays(newArr []interface{}, old []interface{}, ctype string) (result
seen[key] = true
}

data := oldItemSet[key]
count := 0
reqCount := data["count"]
val2 := data["value"]
val2 := oldItemSet[key].value
// for each list item in the existing array, iterate through the template array and try to find a match
for newArrIdx, val1 := range newArrCopy {
if idxWritten[newArrIdx] {
Expand Down Expand Up @@ -2353,14 +2353,14 @@ func mergeArrays(newArr []interface{}, old []interface{}, ctype string) (result

// If the result of merging val1 (template) into val2 (existing value) matched val2 for the required count,
// move on to the next existing value.
if count == reqCount {
if count == oldItemSet[key].count {
break
}
}
// if an item in the existing object cannot be found in the template, we add it to the template array
// to produce the merged array
if count < reqCount.(int) {
for i := 0; i < (reqCount.(int) - count); i++ {
if count < oldItemSet[key].count {
for i := 0; i < (oldItemSet[key].count - count); i++ {
newArr = append(newArr, val2)
}
}
Expand Down Expand Up @@ -2423,7 +2423,7 @@ func handleSingleKey(

updateNeeded := false

if isDenylisted(key) {
if key == "apiVersion" || key == "kind" {
log.V(2).Info("Ignoring the key since it is deny listed", "key", key)

return "", false, nil, true
Expand Down Expand Up @@ -2594,10 +2594,15 @@ func (r *ConfigurationPolicyReconciler) checkAndUpdateResource(

// only look at labels and annotations for metadata - configurationPolicies do not update other metadata fields
if key == "metadata" {
mergedAnnotations := mergedObj.(map[string]interface{})["annotations"]
mergedLabels := mergedObj.(map[string]interface{})["labels"]
obj.object.UnstructuredContent()["metadata"].(map[string]interface{})["annotations"] = mergedAnnotations
obj.object.UnstructuredContent()["metadata"].(map[string]interface{})["labels"] = mergedLabels
// if it's not the right type, the map will be empty
mdMap, _ := mergedObj.(map[string]interface{})

// if either isn't found, they'll just be empty
mergedAnnotations, _, _ := unstructured.NestedStringMap(mdMap, "annotations")
mergedLabels, _, _ := unstructured.NestedStringMap(mdMap, "labels")

obj.object.SetAnnotations(mergedAnnotations)
obj.object.SetLabels(mergedLabels)
} else {
obj.object.UnstructuredContent()[key] = mergedObj
}
Expand Down
10 changes: 5 additions & 5 deletions controllers/configurationpolicy_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -458,11 +458,11 @@ func TestCreateInformStatus(t *testing.T) {
},
}
objNamespaced := true
objData := map[string]interface{}{
"indx": 0,
"kind": "Secret",
"desiredName": "myobject",
"namespaced": objNamespaced,
objData := templateIdentifier{
index: 0,
kind: "Secret",
desiredName: "myobject",
namespaced: objNamespaced,
}
mustNotHave := false
numCompliant := 0
Expand Down
Loading