Example #1
0
// 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
}
Example #2
0
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)
	}
}
Example #3
0
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)
	}
}
Example #4
0
// 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
}
Example #5
0
// 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
}
Example #6
0
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)
	}
}
Example #7
0
// 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
}
Example #8
0
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)
	}

}
Example #9
0
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)
	}
}
Example #10
0
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)
	}
}
Example #11
0
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
}
Example #12
0
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)
	}
}
Example #13
0
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)
}
Example #14
0
// 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
}