package analysis import ( "fmt" "github.com/gonum/graph" osgraph "github.com/openshift/origin/pkg/api/graph" kubeedges "github.com/openshift/origin/pkg/api/kubegraph" kubegraph "github.com/openshift/origin/pkg/api/kubegraph/nodes" ) const ( UnmountableSecretWarning = "UnmountableSecret" MissingSecretWarning = "MissingSecret" MissingLivenessProbeWarning = "MissingLivenessProbe" ) // FindUnmountableSecrets inspects all PodSpecs for any Secret reference that isn't listed as mountable by the referenced ServiceAccount func FindUnmountableSecrets(g osgraph.Graph, f osgraph.Namer) []osgraph.Marker { markers := []osgraph.Marker{} for _, uncastPodSpecNode := range g.NodesByKind(kubegraph.PodSpecNodeKind) { podSpecNode := uncastPodSpecNode.(*kubegraph.PodSpecNode) unmountableSecrets := CheckForUnmountableSecrets(g, podSpecNode) topLevelNode := osgraph.GetTopLevelContainerNode(g, podSpecNode) topLevelString := f.ResourceName(topLevelNode) saString := "MISSING_SA" saNodes := g.SuccessorNodesByEdgeKind(podSpecNode, kubeedges.ReferencedServiceAccountEdgeKind) if len(saNodes) > 0 { saString = f.ResourceName(saNodes[0]) } for _, unmountableSecret := range unmountableSecrets { markers = append(markers, osgraph.Marker{ Node: podSpecNode, RelatedNodes: []graph.Node{unmountableSecret}, Severity: osgraph.WarningSeverity, Key: UnmountableSecretWarning, Message: fmt.Sprintf("%s is attempting to mount a secret %s disallowed by %s", topLevelString, f.ResourceName(unmountableSecret), saString), }) } } return markers } // FindMissingSecrets inspects all PodSpecs for any Secret reference that is a synthetic node (not a pre-existing node in the graph) func FindMissingSecrets(g osgraph.Graph, f osgraph.Namer) []osgraph.Marker { markers := []osgraph.Marker{} for _, uncastPodSpecNode := range g.NodesByKind(kubegraph.PodSpecNodeKind) { podSpecNode := uncastPodSpecNode.(*kubegraph.PodSpecNode) missingSecrets := CheckMissingMountedSecrets(g, podSpecNode) topLevelNode := osgraph.GetTopLevelContainerNode(g, podSpecNode) topLevelString := f.ResourceName(topLevelNode) for _, missingSecret := range missingSecrets { markers = append(markers, osgraph.Marker{ Node: podSpecNode, RelatedNodes: []graph.Node{missingSecret}, Severity: osgraph.WarningSeverity, Key: UnmountableSecretWarning, Message: fmt.Sprintf("%s is attempting to mount a missing secret %s", topLevelString, f.ResourceName(missingSecret)), }) } } return markers } // FindMissingLivenessProbes inspects all PodSpecs for missing liveness probes and generates a list of non-duplicate markers func FindMissingLivenessProbes(g osgraph.Graph, f osgraph.Namer, setProbeCommand string) []osgraph.Marker { markers := []osgraph.Marker{} for _, uncastPodSpecNode := range g.NodesByKind(kubegraph.PodSpecNodeKind) { podSpecNode := uncastPodSpecNode.(*kubegraph.PodSpecNode) if hasLivenessProbe(podSpecNode) { continue } topLevelNode := osgraph.GetTopLevelContainerNode(g, podSpecNode) // skip any podSpec nodes that are managed by other nodes. // Liveness probes should only be applied to a controlling // podSpec node, and not to any of its children. if hasControllerRefEdge(g, topLevelNode) { continue } topLevelString := f.ResourceName(topLevelNode) markers = append(markers, osgraph.Marker{ Node: podSpecNode, RelatedNodes: []graph.Node{topLevelNode}, Severity: osgraph.InfoSeverity, Key: MissingLivenessProbeWarning, Message: fmt.Sprintf("%s has no liveness probe to verify pods are still running.", topLevelString), Suggestion: osgraph.Suggestion(fmt.Sprintf("%s %s --liveness ...", setProbeCommand, topLevelString)), }) } return markers } // hasLivenessProbe iterates through all of the containers in a podSpecNode returning true // if at least one container has a liveness probe, or false otherwise func hasLivenessProbe(podSpecNode *kubegraph.PodSpecNode) bool { for _, container := range podSpecNode.PodSpec.Containers { if container.LivenessProbe != nil { return true } } return false } // hasControllerRefEdge returns true if a given node contains one or more "ManagedByController" outbound edges func hasControllerRefEdge(g osgraph.Graph, node graph.Node) bool { managedEdges := g.OutboundEdges(node, kubeedges.ManagedByControllerEdgeKind) return len(managedEdges) > 0 } // CheckForUnmountableSecrets checks to be sure that all the referenced secrets are mountable (by service account) func CheckForUnmountableSecrets(g osgraph.Graph, podSpecNode *kubegraph.PodSpecNode) []*kubegraph.SecretNode { saNodes := g.SuccessorNodesByNodeAndEdgeKind(podSpecNode, kubegraph.ServiceAccountNodeKind, kubeedges.ReferencedServiceAccountEdgeKind) saMountableSecrets := []*kubegraph.SecretNode{} if len(saNodes) > 0 { saNode := saNodes[0].(*kubegraph.ServiceAccountNode) for _, secretNode := range g.SuccessorNodesByNodeAndEdgeKind(saNode, kubegraph.SecretNodeKind, kubeedges.MountableSecretEdgeKind) { saMountableSecrets = append(saMountableSecrets, secretNode.(*kubegraph.SecretNode)) } } unmountableSecrets := []*kubegraph.SecretNode{} for _, uncastMountedSecretNode := range g.SuccessorNodesByNodeAndEdgeKind(podSpecNode, kubegraph.SecretNodeKind, kubeedges.MountedSecretEdgeKind) { mountedSecretNode := uncastMountedSecretNode.(*kubegraph.SecretNode) mountable := false for _, mountableSecretNode := range saMountableSecrets { if mountableSecretNode == mountedSecretNode { mountable = true break } } if !mountable { unmountableSecrets = append(unmountableSecrets, mountedSecretNode) continue } } return unmountableSecrets } // CheckMissingMountedSecrets checks to be sure that all the referenced secrets are present (not synthetic) func CheckMissingMountedSecrets(g osgraph.Graph, podSpecNode *kubegraph.PodSpecNode) []*kubegraph.SecretNode { missingSecrets := []*kubegraph.SecretNode{} for _, uncastMountedSecretNode := range g.SuccessorNodesByNodeAndEdgeKind(podSpecNode, kubegraph.SecretNodeKind, kubeedges.MountedSecretEdgeKind) { mountedSecretNode := uncastMountedSecretNode.(*kubegraph.SecretNode) if !mountedSecretNode.Found() { missingSecrets = append(missingSecrets, mountedSecretNode) } } return missingSecrets }