// NewServiceGroup returns the ServiceGroup and a set of all the NodeIDs covered by the service func NewServiceGroup(g osgraph.Graph, serviceNode *kubegraph.ServiceNode) (ServiceGroup, IntSet) { covered := IntSet{} covered.Insert(serviceNode.ID()) service := ServiceGroup{} service.Service = serviceNode for _, uncastServiceFulfiller := range g.PredecessorNodesByEdgeKind(serviceNode, kubeedges.ExposedThroughServiceEdgeKind) { container := osgraph.GetTopLevelContainerNode(g, uncastServiceFulfiller) switch castContainer := container.(type) { case *deploygraph.DeploymentConfigNode: service.FulfillingDCs = append(service.FulfillingDCs, castContainer) case *kubegraph.ReplicationControllerNode: service.FulfillingRCs = append(service.FulfillingRCs, castContainer) case *kubegraph.PodNode: service.FulfillingPods = append(service.FulfillingPods, castContainer) default: utilruntime.HandleError(fmt.Errorf("unrecognized container: %v", castContainer)) } } for _, uncastServiceFulfiller := range g.PredecessorNodesByEdgeKind(serviceNode, routeedges.ExposedThroughRouteEdgeKind) { container := osgraph.GetTopLevelContainerNode(g, uncastServiceFulfiller) switch castContainer := container.(type) { case *routegraph.RouteNode: service.ExposingRoutes = append(service.ExposingRoutes, castContainer) default: utilruntime.HandleError(fmt.Errorf("unrecognized container: %v", castContainer)) } } // add the DCPipelines for all the DCs that fulfill the service for _, fulfillingDC := range service.FulfillingDCs { dcPipeline, dcCovers := NewDeploymentConfigPipeline(g, fulfillingDC) covered.Insert(dcCovers.List()...) service.DeploymentConfigPipelines = append(service.DeploymentConfigPipelines, dcPipeline) } for _, fulfillingRC := range service.FulfillingRCs { rcView, rcCovers := NewReplicationController(g, fulfillingRC) covered.Insert(rcCovers.List()...) service.ReplicationControllers = append(service.ReplicationControllers, rcView) } for _, fulfillingPod := range service.FulfillingPods { _, podCovers := NewPod(g, fulfillingPod) covered.Insert(podCovers.List()...) } return service, covered }
func TestDuelingRC(t *testing.T) { g, _, err := osgraphtest.BuildGraph("../../../api/graph/test/dueling-rcs.yaml") if err != nil { t.Fatalf("unexpected error: %v", err) } kubeedges.AddAllManagedByControllerPodEdges(g) markers := FindDuelingReplicationControllers(g, osgraph.DefaultNamer) if e, a := 2, len(markers); e != a { t.Errorf("expected %v, got %v", e, a) } expectedRC1 := g.Find(osgraph.UniqueName("ReplicationController|/rc-1")) expectedRC2 := g.Find(osgraph.UniqueName("ReplicationController|/rc-2")) found1 := false found2 := false for i := 0; i < 2; i++ { actualPod := osgraph.GetTopLevelContainerNode(g, markers[i].RelatedNodes[0]) expectedPod := g.Find(osgraph.UniqueName("Pod|/conflicted-pod")) if e, a := expectedPod.ID(), actualPod.ID(); e != a { t.Errorf("expected %v, got %v", e, a) } actualOtherRC := osgraph.GetTopLevelContainerNode(g, markers[i].RelatedNodes[1]) actualRC := markers[i].Node if e, a := expectedRC1.ID(), actualRC.ID(); e == a { found1 = true expectedOtherRC := expectedRC2 if e, a := expectedOtherRC.ID(), actualOtherRC.ID(); e != a { t.Errorf("expected %v, got %v", e, a) } } if e, a := expectedRC2.ID(), actualRC.ID(); e == a { found2 = true expectedOtherRC := expectedRC1 if e, a := expectedOtherRC.ID(), actualOtherRC.ID(); e != a { t.Errorf("expected %v, got %v", e, a) } } } if !found1 { t.Errorf("expected %v, got %v", expectedRC1, markers) } if !found2 { t.Errorf("expected %v, got %v", expectedRC2, markers) } }
func TestMissingSecrets(t *testing.T) { g, _, err := osgraphtest.BuildGraph("../../../api/graph/test/bad_secret_refs.yaml") if err != nil { t.Fatalf("unexpected error: %v", err) } kubeedges.AddAllRequestedServiceAccountEdges(g) kubeedges.AddAllMountableSecretEdges(g) kubeedges.AddAllMountedSecretEdges(g) markers := FindMissingSecrets(g, osgraph.DefaultNamer) if e, a := 1, len(markers); e != a { t.Fatalf("expected %v, got %v", e, a) } actualDC := osgraph.GetTopLevelContainerNode(g, markers[0].Node) expectedDC := g.Find(osgraph.UniqueName("DeploymentConfig|/docker-nfs-server")) if e, a := expectedDC.ID(), actualDC.ID(); e != a { t.Errorf("expected %v, got %v", e, a) } actualSecret := markers[0].RelatedNodes[0] expectedSecret := g.Find(osgraph.UniqueName("Secret|/missing-secret")) if e, a := expectedSecret.ID(), actualSecret.ID(); e != a { t.Errorf("expected %v, got %v", e, a) } }
// 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) []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 := g.Name(topLevelNode) if resourceStringer, ok := topLevelNode.(osgraph.ResourceNode); ok { topLevelString = resourceStringer.ResourceString() } 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, missingSecret.ResourceString()), }) } } return markers }
// FindUnmountableSecrets inspects all PodSpecs for any Secret reference that isn't listed as mountable by the referenced ServiceAccount func FindUnmountableSecrets(g osgraph.Graph) []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 := g.Name(topLevelNode) if resourceStringer, ok := topLevelNode.(osgraph.ResourceNode); ok { topLevelString = resourceStringer.ResourceString() } saString := "MISSING_SA" saNodes := g.SuccessorNodesByEdgeKind(podSpecNode, kubeedges.ReferencedServiceAccountEdgeKind) if len(saNodes) > 0 { saString = saNodes[0].(*kubegraph.ServiceAccountNode).ResourceString() } 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, unmountableSecret.ResourceString(), saString), }) } } return markers }
func AddMountedSecretEdges(g osgraph.Graph, podSpec *kubegraph.PodSpecNode) { //pod specs are always contained. We'll get the toplevel container so that we can pull a namespace from it containerNode := osgraph.GetTopLevelContainerNode(g, podSpec) containerObj := g.GraphDescriber.Object(containerNode) meta, err := kapi.ObjectMetaFor(containerObj.(runtime.Object)) if err != nil { // this should never happen. it means that a podSpec is owned by a top level container that is not a runtime.Object panic(err) } for _, volume := range podSpec.Volumes { source := volume.VolumeSource if source.Secret == nil { continue } // pod secrets must be in the same namespace syntheticSecret := &kapi.Secret{} syntheticSecret.Namespace = meta.Namespace syntheticSecret.Name = source.Secret.SecretName secretNode := kubegraph.FindOrCreateSyntheticSecretNode(g, syntheticSecret) g.AddEdge(podSpec, secretNode, MountedSecretEdgeKind) } }
// 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 }
func TestUnpushableBuild(t *testing.T) { g, _, err := osgraphtest.BuildGraph("../../../api/graph/test/unpushable-build.yaml") if err != nil { t.Fatalf("unexpected error: %v", err) } buildedges.AddAllInputOutputEdges(g) imageedges.AddAllImageStreamRefEdges(g) markers := FindUnpushableBuildConfigs(g) if e, a := 1, len(markers); e != a { t.Fatalf("expected %v, got %v", e, a) } actualBC := osgraph.GetTopLevelContainerNode(g, markers[0].Node) expectedBC := g.Find(osgraph.UniqueName("BuildConfig|/ruby-hello-world")) if e, a := expectedBC.ID(), actualBC.ID(); e != a { t.Errorf("expected %v, got %v", e, a) } actualIST := markers[0].RelatedNodes[0] expectedIST := g.Find(osgraph.UniqueName("ImageStreamTag|/ruby-hello-world:latest")) if e, a := expectedIST.ID(), actualIST.ID(); e != a { t.Errorf("expected %v, got %v: \n%v", e, a, g) } }
func TestUnpushableBuild(t *testing.T) { // Unconfigured internal registry g, _, err := osgraphtest.BuildGraph("../../../api/graph/test/unpushable-build.yaml") if err != nil { t.Fatalf("unexpected error: %v", err) } buildedges.AddAllInputOutputEdges(g) imageedges.AddAllImageStreamRefEdges(g) imageedges.AddAllImageStreamImageRefEdges(g) markers := FindUnpushableBuildConfigs(g, osgraph.DefaultNamer) if e, a := 2, len(markers); e != a { t.Fatalf("expected %v, got %v", e, a) } if got, expected := markers[0].Key, MissingRequiredRegistryErr; got != expected { t.Fatalf("expected marker key %q, got %q", expected, got) } actualBC := osgraph.GetTopLevelContainerNode(g, markers[0].Node) expectedBC1 := g.Find(osgraph.UniqueName("BuildConfig|example/ruby-hello-world")) expectedBC2 := g.Find(osgraph.UniqueName("BuildConfig|example/ruby-hello-world-2")) if e1, e2, a := expectedBC1.ID(), expectedBC2.ID(), actualBC.ID(); e1 != a && e2 != a { t.Errorf("expected either %v or %v, got %v", e1, e2, a) } actualIST := markers[0].RelatedNodes[0] expectedIST := g.Find(osgraph.UniqueName("ImageStreamTag|example/ruby-hello-world:latest")) if e, a := expectedIST.ID(), actualIST.ID(); e != a { t.Errorf("expected %v, got %v: \n%v", e, a, g) } // Missing image stream g, _, err = osgraphtest.BuildGraph("../../../api/graph/test/unpushable-build-2.yaml") if err != nil { t.Fatalf("unexpected error: %v", err) } buildedges.AddAllInputOutputEdges(g) imageedges.AddAllImageStreamRefEdges(g) imageedges.AddAllImageStreamImageRefEdges(g) markers = FindUnpushableBuildConfigs(g, osgraph.DefaultNamer) if e, a := 1, len(markers); e != a { t.Fatalf("expected %v, got %v", e, a) } if got, expected := markers[0].Key, MissingOutputImageStreamErr; got != expected { t.Fatalf("expected marker key %q, got %q", expected, got) } }
func TestUnmountableSecrets(t *testing.T) { g, _, err := osgraphtest.BuildGraph("../../../api/graph/test/bad_secret_refs.yaml") if err != nil { t.Fatalf("unexpected error: %v", err) } kubeedges.AddAllRequestedServiceAccountEdges(g) kubeedges.AddAllMountableSecretEdges(g) kubeedges.AddAllMountedSecretEdges(g) markers := FindUnmountableSecrets(g, osgraph.DefaultNamer) if e, a := 2, len(markers); e != a { t.Errorf("expected %v, got %v", e, a) } expectedSecret1 := g.Find(osgraph.UniqueName("Secret|/missing-secret")) expectedSecret2 := g.Find(osgraph.UniqueName("Secret|/unmountable-secret")) found1 := false found2 := false for i := 0; i < 2; i++ { actualDC := osgraph.GetTopLevelContainerNode(g, markers[i].Node) expectedDC := g.Find(osgraph.UniqueName("DeploymentConfig|/docker-nfs-server")) if e, a := expectedDC.ID(), actualDC.ID(); e != a { t.Errorf("expected %v, got %v", e, a) } actualSecret := markers[i].RelatedNodes[0] if e, a := expectedSecret1.ID(), actualSecret.ID(); e == a { found1 = true } if e, a := expectedSecret2.ID(), actualSecret.ID(); e == a { found2 = true } } if !found1 { t.Errorf("expected %v, got %v", expectedSecret1, markers) } if !found2 { t.Errorf("expected %v, got %v", expectedSecret2, markers) } }
func describeBadPodSpecs(out io.Writer, g osgraph.Graph) ([]string, []*kubegraph.SecretNode) { allMissingSecrets := []*kubegraph.SecretNode{} lines := []string{} for _, uncastPodSpec := range g.NodesByKind(kubegraph.PodSpecNodeKind) { podSpecNode := uncastPodSpec.(*kubegraph.PodSpecNode) unmountableSecrets, missingSecrets := kubeanalysis.CheckMountedSecrets(g, podSpecNode) containingNode := osgraph.GetTopLevelContainerNode(g, podSpecNode) allMissingSecrets = append(allMissingSecrets, missingSecrets...) unmountableNames := []string{} for _, secret := range unmountableSecrets { unmountableNames = append(unmountableNames, secret.ResourceString()) } missingNames := []string{} for _, secret := range missingSecrets { missingNames = append(missingNames, secret.ResourceString()) } containingNodeName := g.GraphDescriber.Name(containingNode) if resourceNode, ok := containingNode.(osgraph.ResourceNode); ok { containingNodeName = resourceNode.ResourceString() } switch { case len(unmountableSecrets) > 0 && len(missingSecrets) > 0: lines = append(lines, fmt.Sprintf("\t%s is not allowed to mount %s and wants to mount these missing secrets %s", containingNodeName, strings.Join(unmountableNames, ","), strings.Join(missingNames, ","))) case len(unmountableSecrets) > 0: lines = append(lines, fmt.Sprintf("\t%s is not allowed to mount %s", containingNodeName, strings.Join(unmountableNames, ","))) case len(unmountableSecrets) > 0 && len(missingSecrets) > 0: lines = append(lines, fmt.Sprintf("\t%s wants to mount these missing secrets %s", containingNodeName, strings.Join(missingNames, ","))) } } // if we had any failures, prepend the warning line if len(lines) > 0 { return append([]string{"Warning: some requested secrets are not allowed:"}, lines...), allMissingSecrets } return []string{}, allMissingSecrets }
func TestMissingLivenessProbes(t *testing.T) { g, _, err := osgraphtest.BuildGraph("../../../api/graph/test/simple-deployment.yaml") if err != nil { t.Fatalf("unexpected error: %v", err) } kubeedges.AddAllExposedPodEdges(g) markers := FindMissingLivenessProbes(g, osgraph.DefaultNamer, "oc set probe") if e, a := 1, len(markers); e != a { t.Fatalf("expected %v, got %v", e, a) } actualDC := osgraph.GetTopLevelContainerNode(g, markers[0].Node) expectedDC := g.Find(osgraph.UniqueName("DeploymentConfig|/simple-deployment")) if e, a := expectedDC.ID(), actualDC.ID(); e != a { t.Errorf("expected %v, got %v", e, a) } }
func AddRequestedServiceAccountEdges(g osgraph.Graph, podSpecNode *kubegraph.PodSpecNode) { //pod specs are always contained. We'll get the toplevel container so that we can pull a namespace from it containerNode := osgraph.GetTopLevelContainerNode(g, podSpecNode) containerObj := g.GraphDescriber.Object(containerNode) meta, err := kapi.ObjectMetaFor(containerObj.(runtime.Object)) if err != nil { panic(err) } // if no SA name is present, admission will set 'default' name := "default" if len(podSpecNode.ServiceAccountName) > 0 { name = podSpecNode.ServiceAccountName } syntheticSA := &kapi.ServiceAccount{} syntheticSA.Namespace = meta.Namespace syntheticSA.Name = name saNode := kubegraph.FindOrCreateSyntheticServiceAccountNode(g, syntheticSA) g.AddEdge(podSpecNode, saNode, ReferencedServiceAccountEdgeKind) }
// ServiceAndDeploymentGroups breaks the provided graph of API relationships into ServiceGroup objects, // ordered consistently. Groups are organized so that overlapping Services and DeploymentConfigs are // part of the same group, Deployment Configs are each in their own group, and then BuildConfigs are // part of the last service group. func ServiceAndDeploymentGroups(g osgraph.Graph) []ServiceGroup { deploys, covered := DeploymentPipelines(g) other := g.Subgraph(UncoveredDeploymentFlowNodes(covered), UncoveredDeploymentFlowEdges(covered)) components := search.Tarjan(other) serviceGroups := []ServiceGroup{} for _, c := range components { group := ServiceGroup{} matches := osgraph.NodesByKind(other, c, kubegraph.ServiceNodeKind, deploygraph.DeploymentConfigNodeKind) svcs, dcs, _ := matches[0], matches[1], matches[2] for _, n := range svcs { coveredDCs := []*deploygraph.DeploymentConfigNode{} coveredRCs := []*kubegraph.ReplicationControllerNode{} coveredPods := []*kubegraph.PodNode{} for _, neighbor := range other.Neighbors(n) { switch other.EdgeKind(g.EdgeBetween(neighbor, n)) { case kubeedges.ExposedThroughServiceEdgeKind: containerNode := osgraph.GetTopLevelContainerNode(g, neighbor) switch castCoveredNode := containerNode.(type) { case *deploygraph.DeploymentConfigNode: coveredDCs = append(coveredDCs, castCoveredNode) case *kubegraph.ReplicationControllerNode: coveredRCs = append(coveredRCs, castCoveredNode) case *kubegraph.PodNode: coveredPods = append(coveredPods, castCoveredNode) } } } group.Services = append(group.Services, ServiceReference{ Service: n.(*kubegraph.ServiceNode), CoveredDCs: coveredDCs, CoveredRCs: coveredRCs, CoveredPods: coveredPods, }) } sort.Sort(SortedServiceReferences(group.Services)) for _, n := range dcs { d := n.(*deploygraph.DeploymentConfigNode) group.Deployments = append(group.Deployments, DeploymentFlow{ Deployment: d, Images: deploys[d], }) } sort.Sort(SortedDeploymentPipelines(group.Deployments)) if len(dcs) == 0 || len(svcs) == 0 { unknown := g.SubgraphWithNodes(c, osgraph.ExistingDirectEdge) for _, n := range unknown.NodeList() { g.PredecessorEdges(n, osgraph.AddGraphEdgesTo(unknown), buildedges.BuildOutputEdgeKind) } unknown = unknown.EdgeSubgraph(osgraph.ReverseGraphEdge) for _, n := range unknown.RootNodes() { if flow, ok := ImagePipelineFromNode(unknown, n, make(osgraph.NodeSet)); ok { group.Builds = append(group.Builds, flow) } } } sort.Sort(SortedImagePipelines(group.Builds)) serviceGroups = append(serviceGroups, group) } sort.Sort(SortedServiceGroups(serviceGroups)) return serviceGroups }