diff --git a/pkg/api/graph/graph.go b/pkg/api/graph/graph.go index 87a622e34772..f9b616504c7a 100644 --- a/pkg/api/graph/graph.go +++ b/pkg/api/graph/graph.go @@ -25,6 +25,10 @@ type ExistenceChecker interface { Found() bool } +type ResourceNode interface { + ResourceString() string +} + type UniqueName string type UniqueNameFunc func(obj interface{}) UniqueName @@ -158,7 +162,7 @@ func (g Graph) SyntheticNodes() []graph.Node { sort.Sort(SortedNodeList(nodeList)) for _, node := range nodeList { if potentiallySyntheticNode, ok := node.(ExistenceChecker); ok { - if potentiallySyntheticNode.Found() { + if !potentiallySyntheticNode.Found() { ret = append(ret, node) } } diff --git a/pkg/api/graph/test/runtimeobject_nodebuilder.go b/pkg/api/graph/test/runtimeobject_nodebuilder.go index 9aae0cc2d453..2ac197a8d665 100644 --- a/pkg/api/graph/test/runtimeobject_nodebuilder.go +++ b/pkg/api/graph/test/runtimeobject_nodebuilder.go @@ -46,6 +46,12 @@ func init() { if err := RegisterEnsureNode(&kapi.Service{}, kubegraph.EnsureServiceNode); err != nil { panic(err) } + if err := RegisterEnsureNode(&kapi.ServiceAccount{}, kubegraph.EnsureServiceAccountNode); err != nil { + panic(err) + } + if err := RegisterEnsureNode(&kapi.Secret{}, kubegraph.EnsureSecretNode); err != nil { + panic(err) + } if err := RegisterEnsureNode(&kapi.ReplicationController{}, kubegraph.EnsureReplicationControllerNode); err != nil { panic(err) } diff --git a/pkg/api/kubegraph/analysis/podspec.go b/pkg/api/kubegraph/analysis/podspec.go new file mode 100644 index 000000000000..b9d4e3c0185b --- /dev/null +++ b/pkg/api/kubegraph/analysis/podspec.go @@ -0,0 +1,45 @@ +package analysis + +import ( + 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" +) + +// CheckMountedSecrets checks to be sure that all the referenced secrets are mountable (by service account) and present (not synthetic) +func CheckMountedSecrets(g osgraph.Graph, podSpecNode *kubegraph.PodSpecNode) ( /*unmountable secrets*/ []*kubegraph.SecretNode /*unresolved secrets*/, []*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{} + missingSecrets := []*kubegraph.SecretNode{} + + for _, uncastMountedSecretNode := range g.SuccessorNodesByNodeAndEdgeKind(podSpecNode, kubegraph.SecretNodeKind, kubeedges.MountedSecretEdgeKind) { + mountedSecretNode := uncastMountedSecretNode.(*kubegraph.SecretNode) + if !mountedSecretNode.Found() { + missingSecrets = append(missingSecrets, mountedSecretNode) + } + + mountable := false + for _, mountableSecretNode := range saMountableSecrets { + if mountableSecretNode == mountedSecretNode { + mountable = true + break + } + } + + if !mountable { + unmountableSecrets = append(unmountableSecrets, mountedSecretNode) + continue + } + } + + return unmountableSecrets, missingSecrets +} diff --git a/pkg/api/kubegraph/edge_test.go b/pkg/api/kubegraph/edge_test.go new file mode 100644 index 000000000000..55362d72ebbf --- /dev/null +++ b/pkg/api/kubegraph/edge_test.go @@ -0,0 +1,55 @@ +package kubegraph + +import ( + "testing" + + kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + + osgraph "github.com/openshift/origin/pkg/api/graph" + kubegraph "github.com/openshift/origin/pkg/api/kubegraph/nodes" +) + +func TestSecretEdges(t *testing.T) { + sa := &kapi.ServiceAccount{} + sa.Namespace = "ns" + sa.Name = "shultz" + sa.Secrets = []kapi.ObjectReference{{Name: "i-know-nothing"}, {Name: "missing"}} + + secret1 := &kapi.Secret{} + secret1.Namespace = "ns" + secret1.Name = "i-know-nothing" + + pod := &kapi.Pod{} + pod.Namespace = "ns" + pod.Name = "the-pod" + pod.Spec.Volumes = []kapi.Volume{{Name: "rose", VolumeSource: kapi.VolumeSource{Secret: &kapi.SecretVolumeSource{SecretName: "i-know-nothing"}}}} + + g := osgraph.New() + saNode := kubegraph.EnsureServiceAccountNode(g, sa) + secretNode := kubegraph.EnsureSecretNode(g, secret1) + podNode := kubegraph.EnsurePodNode(g, pod) + + AddAllMountableSecretEdges(g) + AddAllMountedSecretEdges(g) + + if edge := g.EdgeBetween(saNode, secretNode); edge == nil { + t.Errorf("edge missing") + } else { + if edgeKind := g.EdgeKind(edge); edgeKind != MountableSecretEdgeKind { + t.Errorf("expected %v, got %v", MountableSecretEdgeKind, edgeKind) + } + } + + podSpecNodes := g.SuccessorNodesByNodeAndEdgeKind(podNode, kubegraph.PodSpecNodeKind, osgraph.ContainsEdgeKind) + if len(podSpecNodes) != 1 { + t.Fatalf("wrong number of podspecs: %v", podSpecNodes) + } + + if edge := g.EdgeBetween(podSpecNodes[0], secretNode); edge == nil { + t.Errorf("edge missing") + } else { + if edgeKind := g.EdgeKind(edge); edgeKind != MountedSecretEdgeKind { + t.Errorf("expected %v, got %v", MountedSecretEdgeKind, edgeKind) + } + } +} diff --git a/pkg/api/kubegraph/edges.go b/pkg/api/kubegraph/edges.go index b5b3eecd6097..510cd2343be0 100644 --- a/pkg/api/kubegraph/edges.go +++ b/pkg/api/kubegraph/edges.go @@ -3,18 +3,25 @@ package kubegraph import ( "github.com/gonum/graph" + kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" osgraph "github.com/openshift/origin/pkg/api/graph" kubegraph "github.com/openshift/origin/pkg/api/kubegraph/nodes" ) const ( - // ExposedThroughServiceEdgeKind is an edge that goes from a podtemplatespec or a pod to service. - // The head should make the service's selector + // ExposedThroughServiceEdgeKind goes from a PodTemplateSpec or a Pod to Service. The head should make the service's selector. ExposedThroughServiceEdgeKind = "ExposedThroughService" // ManagedByRCEdgeKind goes from Pod to ReplicationController when the Pod satisfies the ReplicationController's label selector ManagedByRCEdgeKind = "ManagedByRC" + // MountedSecretEdgeKind goes from PodSpec to Secret indicating that is or will be a request to mount a volume with the Secret. + MountedSecretEdgeKind = "MountedSecret" + // MountableSecretEdgeKind goes from ServiceAccount to Secret indicating that the SA allows the Secret to be mounted + MountableSecretEdgeKind = "MountableSecret" + // ReferencedServiceAccountEdgeKind goes from PodSpec to ServiceAccount indicating that Pod is or will be running as the SA. + ReferencedServiceAccountEdgeKind = "ReferencedServiceAccount" ) // AddExposedPodTemplateSpecEdges ensures that a directed edge exists between a service and all the PodTemplateSpecs @@ -94,3 +101,89 @@ func AddAllManagedByRCPodEdges(g osgraph.MutableUniqueGraph) { } } } + +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) + } +} + +func AddAllMountedSecretEdges(g osgraph.Graph) { + for _, node := range g.NodeList() { + if podSpecNode, ok := node.(*kubegraph.PodSpecNode); ok { + AddMountedSecretEdges(g, podSpecNode) + } + } +} + +func AddMountableSecretEdges(g osgraph.Graph, saNode *kubegraph.ServiceAccountNode) { + for _, mountableSecret := range saNode.ServiceAccount.Secrets { + syntheticSecret := &kapi.Secret{} + syntheticSecret.Namespace = saNode.ServiceAccount.Namespace + syntheticSecret.Name = mountableSecret.Name + + secretNode := kubegraph.FindOrCreateSyntheticSecretNode(g, syntheticSecret) + g.AddEdge(saNode, secretNode, MountableSecretEdgeKind) + } +} + +func AddAllMountableSecretEdges(g osgraph.Graph) { + for _, node := range g.NodeList() { + if saNode, ok := node.(*kubegraph.ServiceAccountNode); ok { + AddMountableSecretEdges(g, saNode) + } + } +} + +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) +} + +func AddAllRequestedServiceAccountEdges(g osgraph.Graph) { + for _, node := range g.NodeList() { + if podSpecNode, ok := node.(*kubegraph.PodSpecNode); ok { + AddRequestedServiceAccountEdges(g, podSpecNode) + } + } +} diff --git a/pkg/api/kubegraph/nodes/nodes.go b/pkg/api/kubegraph/nodes/nodes.go index 8287c2311ce4..429b5f792149 100644 --- a/pkg/api/kubegraph/nodes/nodes.go +++ b/pkg/api/kubegraph/nodes/nodes.go @@ -42,6 +42,42 @@ func EnsureServiceNode(g osgraph.MutableUniqueGraph, svc *kapi.Service) *Service ).(*ServiceNode) } +func EnsureServiceAccountNode(g osgraph.MutableUniqueGraph, o *kapi.ServiceAccount) *ServiceAccountNode { + return osgraph.EnsureUnique(g, + ServiceAccountNodeName(o), + func(node osgraph.Node) graph.Node { + return &ServiceAccountNode{node, o, true} + }, + ).(*ServiceAccountNode) +} + +func FindOrCreateSyntheticServiceAccountNode(g osgraph.MutableUniqueGraph, o *kapi.ServiceAccount) *ServiceAccountNode { + return osgraph.EnsureUnique(g, + ServiceAccountNodeName(o), + func(node osgraph.Node) graph.Node { + return &ServiceAccountNode{node, o, false} + }, + ).(*ServiceAccountNode) +} + +func EnsureSecretNode(g osgraph.MutableUniqueGraph, o *kapi.Secret) *SecretNode { + return osgraph.EnsureUnique(g, + SecretNodeName(o), + func(node osgraph.Node) graph.Node { + return &SecretNode{node, o, true} + }, + ).(*SecretNode) +} + +func FindOrCreateSyntheticSecretNode(g osgraph.MutableUniqueGraph, o *kapi.Secret) *SecretNode { + return osgraph.EnsureUnique(g, + SecretNodeName(o), + func(node osgraph.Node) graph.Node { + return &SecretNode{node, o, false} + }, + ).(*SecretNode) +} + // EnsureReplicationControllerNode adds a graph node for the ReplicationController if it does not already exist. func EnsureReplicationControllerNode(g osgraph.MutableUniqueGraph, rc *kapi.ReplicationController) *ReplicationControllerNode { rcNodeName := ReplicationControllerNodeName(rc) diff --git a/pkg/api/kubegraph/nodes/types.go b/pkg/api/kubegraph/nodes/types.go index cd2782ed4833..8691a7733402 100644 --- a/pkg/api/kubegraph/nodes/types.go +++ b/pkg/api/kubegraph/nodes/types.go @@ -16,6 +16,8 @@ var ( PodTemplateSpecNodeKind = reflect.TypeOf(kapi.PodTemplateSpec{}).Name() ReplicationControllerNodeKind = reflect.TypeOf(kapi.ReplicationController{}).Name() ReplicationControllerSpecNodeKind = reflect.TypeOf(kapi.ReplicationControllerSpec{}).Name() + ServiceAccountNodeKind = reflect.TypeOf(kapi.ServiceAccount{}).Name() + SecretNodeKind = reflect.TypeOf(kapi.Secret{}).Name() ) func ServiceNodeName(o *kapi.Service) osgraph.UniqueName { @@ -35,6 +37,10 @@ func (n ServiceNode) String() string { return string(ServiceNodeName(n.Service)) } +func (n ServiceNode) ResourceString() string { + return "svc/" + n.Name +} + func (*ServiceNode) Kind() string { return ServiceNodeKind } @@ -56,6 +62,10 @@ func (n PodNode) String() string { return string(PodNodeName(n.Pod)) } +func (n PodNode) ResourceString() string { + return "pod/" + n.Name +} + func (n PodNode) UniqueName() osgraph.UniqueName { return PodNodeName(n.Pod) } @@ -108,6 +118,10 @@ func (n ReplicationControllerNode) String() string { return string(ReplicationControllerNodeName(n.ReplicationController)) } +func (n ReplicationControllerNode) ResourceString() string { + return "rc/" + n.Name +} + func (n ReplicationControllerNode) UniqueName() osgraph.UniqueName { return ReplicationControllerNodeName(n.ReplicationController) } @@ -169,3 +183,65 @@ func (n PodTemplateSpecNode) UniqueName() osgraph.UniqueName { func (*PodTemplateSpecNode) Kind() string { return PodTemplateSpecNodeKind } + +func ServiceAccountNodeName(o *kapi.ServiceAccount) osgraph.UniqueName { + return osgraph.GetUniqueRuntimeObjectNodeName(ServiceAccountNodeKind, o) +} + +type ServiceAccountNode struct { + osgraph.Node + *kapi.ServiceAccount + + IsFound bool +} + +func (n ServiceAccountNode) Found() bool { + return n.IsFound +} + +func (n ServiceAccountNode) Object() interface{} { + return n.ServiceAccount +} + +func (n ServiceAccountNode) String() string { + return string(ServiceAccountNodeName(n.ServiceAccount)) +} + +func (n ServiceAccountNode) ResourceString() string { + return "sa/" + n.Name +} + +func (*ServiceAccountNode) Kind() string { + return ServiceAccountNodeKind +} + +func SecretNodeName(o *kapi.Secret) osgraph.UniqueName { + return osgraph.GetUniqueRuntimeObjectNodeName(SecretNodeKind, o) +} + +type SecretNode struct { + osgraph.Node + *kapi.Secret + + IsFound bool +} + +func (n SecretNode) Found() bool { + return n.IsFound +} + +func (n SecretNode) Object() interface{} { + return n.Secret +} + +func (n SecretNode) String() string { + return string(SecretNodeName(n.Secret)) +} + +func (n SecretNode) ResourceString() string { + return "secret/" + n.Name +} + +func (*SecretNode) Kind() string { + return SecretNodeKind +} diff --git a/pkg/build/graph/nodes/types.go b/pkg/build/graph/nodes/types.go index 81527982953a..79416f201f2e 100644 --- a/pkg/build/graph/nodes/types.go +++ b/pkg/build/graph/nodes/types.go @@ -33,6 +33,10 @@ func (n BuildConfigNode) String() string { return string(BuildConfigNodeName(n.BuildConfig)) } +func (n BuildConfigNode) ResourceString() string { + return "bc/" + n.Name +} + func (*BuildConfigNode) Kind() string { return BuildConfigNodeKind } @@ -77,6 +81,10 @@ func (n BuildNode) String() string { return string(BuildNodeName(n.Build)) } +func (n BuildNode) ResourceString() string { + return "build/" + n.Build.Name +} + func (*BuildNode) Kind() string { return BuildNodeKind } diff --git a/pkg/cmd/cli/describe/projectstatus.go b/pkg/cmd/cli/describe/projectstatus.go index 4345f684c4f9..4deee69f54d2 100644 --- a/pkg/cmd/cli/describe/projectstatus.go +++ b/pkg/cmd/cli/describe/projectstatus.go @@ -16,6 +16,7 @@ import ( osgraph "github.com/openshift/origin/pkg/api/graph" "github.com/openshift/origin/pkg/api/graph/graphview" kubeedges "github.com/openshift/origin/pkg/api/kubegraph" + kubeanalysis "github.com/openshift/origin/pkg/api/kubegraph/analysis" kubegraph "github.com/openshift/origin/pkg/api/kubegraph/nodes" buildapi "github.com/openshift/origin/pkg/build/api" buildedges "github.com/openshift/origin/pkg/build/graph" @@ -43,6 +44,8 @@ func (d *ProjectStatusDescriber) MakeGraph(namespace string) (osgraph.Graph, err loaders := []GraphLoader{ &serviceLoader{namespace: namespace, lister: d.K}, + &serviceAccountLoader{namespace: namespace, lister: d.K}, + &secretLoader{namespace: namespace, lister: d.K}, &rcLoader{namespace: namespace, lister: d.K}, &podLoader{namespace: namespace, lister: d.K}, &bcLoader{namespace: namespace, lister: d.C}, @@ -71,6 +74,9 @@ func (d *ProjectStatusDescriber) MakeGraph(namespace string) (osgraph.Graph, err deployedges.AddAllTriggerEdges(g) deployedges.AddAllDeploymentEdges(g) imageedges.AddAllImageStreamRefEdges(g) + kubeedges.AddAllRequestedServiceAccountEdges(g) + kubeedges.AddAllMountableSecretEdges(g) + kubeedges.AddAllMountedSecretEdges(g) return g, nil } @@ -154,6 +160,9 @@ func (d *ProjectStatusDescriber) Describe(namespace, name string) (string, error if hasUnresolvedImageStreamTag(g) { fmt.Fprintln(out, "Warning: Some of your builds are pointing to image streams, but the administrator has not configured the integrated Docker registry (oadm registry).") } + if lines, _ := describeBadPodSpecs(out, g); len(lines) > 0 { + fmt.Fprintln(out, strings.Join(lines, "\n")) + } fmt.Fprintln(out, "To see more, use 'oc describe service ' or 'oc describe dc '.") fmt.Fprintln(out, "You can use 'oc get all' to see a list of other objects.") @@ -179,6 +188,50 @@ func hasUnresolvedImageStreamTag(g osgraph.Graph) bool { return false } +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 printLines(out io.Writer, indent string, depth int, lines ...string) { for i, s := range lines { fmt.Fprintf(out, strings.Repeat(indent, depth)) @@ -625,14 +678,14 @@ type GraphLoader interface { AddToGraph(g osgraph.Graph) error } -type serviceLoader struct { +type rcLoader struct { namespace string - lister kclient.ServicesNamespacer - items []kapi.Service + lister kclient.ReplicationControllersNamespacer + items []kapi.ReplicationController } -func (l *serviceLoader) Load() error { - list, err := l.lister.Services(l.namespace).List(labels.Everything()) +func (l *rcLoader) Load() error { + list, err := l.lister.ReplicationControllers(l.namespace).List(labels.Everything()) if err != nil { return err } @@ -641,22 +694,22 @@ func (l *serviceLoader) Load() error { return nil } -func (l *serviceLoader) AddToGraph(g osgraph.Graph) error { +func (l *rcLoader) AddToGraph(g osgraph.Graph) error { for i := range l.items { - kubegraph.EnsureServiceNode(g, &l.items[i]) + kubegraph.EnsureReplicationControllerNode(g, &l.items[i]) } return nil } -type rcLoader struct { +type serviceLoader struct { namespace string - lister kclient.ReplicationControllersNamespacer - items []kapi.ReplicationController + lister kclient.ServicesNamespacer + items []kapi.Service } -func (l *rcLoader) Load() error { - list, err := l.lister.ReplicationControllers(l.namespace).List(labels.Everything()) +func (l *serviceLoader) Load() error { + list, err := l.lister.Services(l.namespace).List(labels.Everything()) if err != nil { return err } @@ -665,9 +718,9 @@ func (l *rcLoader) Load() error { return nil } -func (l *rcLoader) AddToGraph(g osgraph.Graph) error { +func (l *serviceLoader) AddToGraph(g osgraph.Graph) error { for i := range l.items { - kubegraph.EnsureReplicationControllerNode(g, &l.items[i]) + kubegraph.EnsureServiceNode(g, &l.items[i]) } return nil @@ -697,6 +750,54 @@ func (l *podLoader) AddToGraph(g osgraph.Graph) error { return nil } +type serviceAccountLoader struct { + namespace string + lister kclient.ServiceAccountsNamespacer + items []kapi.ServiceAccount +} + +func (l *serviceAccountLoader) Load() error { + list, err := l.lister.ServiceAccounts(l.namespace).List(labels.Everything(), fields.Everything()) + if err != nil { + return err + } + + l.items = list.Items + return nil +} + +func (l *serviceAccountLoader) AddToGraph(g osgraph.Graph) error { + for i := range l.items { + kubegraph.EnsureServiceAccountNode(g, &l.items[i]) + } + + return nil +} + +type secretLoader struct { + namespace string + lister kclient.SecretsNamespacer + items []kapi.Secret +} + +func (l *secretLoader) Load() error { + list, err := l.lister.Secrets(l.namespace).List(labels.Everything(), fields.Everything()) + if err != nil { + return err + } + + l.items = list.Items + return nil +} + +func (l *secretLoader) AddToGraph(g osgraph.Graph) error { + for i := range l.items { + kubegraph.EnsureSecretNode(g, &l.items[i]) + } + + return nil +} + type isLoader struct { namespace string lister client.ImageStreamsNamespacer diff --git a/pkg/deploy/graph/analysis/dc.go b/pkg/deploy/graph/analysis/dc.go new file mode 100644 index 000000000000..88b619ec77d6 --- /dev/null +++ b/pkg/deploy/graph/analysis/dc.go @@ -0,0 +1,53 @@ +package analysis + +import ( + "github.com/gonum/graph" + + osgraph "github.com/openshift/origin/pkg/api/graph" + "github.com/openshift/origin/pkg/api/graph/graphview" + kubeanalysis "github.com/openshift/origin/pkg/api/kubegraph/analysis" + kubegraph "github.com/openshift/origin/pkg/api/kubegraph/nodes" + deploygraph "github.com/openshift/origin/pkg/deploy/graph/nodes" +) + +// DescendentNodesByNodeKind starts at the root navigates down the root. Every edge is checked against the edgeChecker +// to determine whether or not to follow it. The nodes at the tail end of every chased edge are then checked against the +// the targetNodeKind. Matches are added to the return and every checked node then has its edges checked: lather, rinse, repeat +func DescendentNodesByNodeKind(g osgraph.Graph, visitedNodes graphview.IntSet, node graph.Node, targetNodeKind string, edgeChecker osgraph.EdgeFunc) []graph.Node { + if visitedNodes.Has(node.ID()) { + return []graph.Node{} + } + visitedNodes.Insert(node.ID()) + + ret := []graph.Node{} + for _, successor := range g.Successors(node) { + edge := g.EdgeBetween(node, successor) + kind := g.EdgeKind(edge) + + if edgeChecker(osgraph.New(), node, successor, kind) { + if g.Kind(successor) == targetNodeKind { + ret = append(ret, successor) + } + + ret = append(ret, DescendentNodesByNodeKind(g, visitedNodes, successor, targetNodeKind, edgeChecker)...) + } + } + + return ret +} + +// CheckMountedSecrets checks to be sure that all the referenced secrets are mountable (by service account) and present (not synthetic) +func CheckMountedSecrets(g osgraph.Graph, dcNode *deploygraph.DeploymentConfigNode) ( /*unmountable secrets*/ []*kubegraph.SecretNode /*unresolved secrets*/, []*kubegraph.SecretNode) { + podSpecs := DescendentNodesByNodeKind(g, graphview.IntSet{}, dcNode, kubegraph.PodSpecNodeKind, func(g osgraph.Interface, head, tail graph.Node, edgeKind string) bool { + if edgeKind == osgraph.ContainsEdgeKind { + return true + } + return false + }) + + if len(podSpecs) > 0 { + return kubeanalysis.CheckMountedSecrets(g, podSpecs[0].(*kubegraph.PodSpecNode)) + } + + return []*kubegraph.SecretNode{}, []*kubegraph.SecretNode{} +} diff --git a/pkg/deploy/graph/analysis/dc_test.go b/pkg/deploy/graph/analysis/dc_test.go new file mode 100644 index 000000000000..dc303b4731dc --- /dev/null +++ b/pkg/deploy/graph/analysis/dc_test.go @@ -0,0 +1,45 @@ +package analysis + +import ( + "testing" + + osgraphtest "github.com/openshift/origin/pkg/api/graph/test" + kubeedges "github.com/openshift/origin/pkg/api/kubegraph" + deployapi "github.com/openshift/origin/pkg/deploy/api" + deploygraph "github.com/openshift/origin/pkg/deploy/graph/nodes" +) + +func TestCheckMountedSecrets(t *testing.T) { + g, objs, err := osgraphtest.BuildGraph("../../../api/graph/test/bad_secret_refs.yaml") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + var dc *deployapi.DeploymentConfig + for _, obj := range objs { + if currDC, ok := obj.(*deployapi.DeploymentConfig); ok { + if dc != nil { + t.Errorf("got more than one dc: %v", currDC) + } + dc = currDC + } + } + + kubeedges.AddAllRequestedServiceAccountEdges(g) + kubeedges.AddAllMountableSecretEdges(g) + kubeedges.AddAllMountedSecretEdges(g) + + dcNode := g.Find(deploygraph.DeploymentConfigNodeName(dc)) + unmountable, missing := CheckMountedSecrets(g, dcNode.(*deploygraph.DeploymentConfigNode)) + + if e, a := 2, len(unmountable); e != a { + t.Fatalf("expected %v, got %v", e, a) + } + + if e, a := 1, len(missing); e != a { + t.Fatalf("expected %v, got %v", e, a) + } + if e, a := "missing-secret", missing[0].Name; e != a { + t.Fatalf("expected %v, got %v", e, a) + } +} diff --git a/pkg/deploy/graph/nodes/types.go b/pkg/deploy/graph/nodes/types.go index 790ba173060a..7bbebca1e985 100644 --- a/pkg/deploy/graph/nodes/types.go +++ b/pkg/deploy/graph/nodes/types.go @@ -28,6 +28,10 @@ func (n DeploymentConfigNode) String() string { return string(DeploymentConfigNodeName(n.DeploymentConfig)) } +func (n DeploymentConfigNode) ResourceString() string { + return "dc/" + n.Name +} + func (*DeploymentConfigNode) Kind() string { return DeploymentConfigNodeKind } diff --git a/pkg/image/graph/nodes/types.go b/pkg/image/graph/nodes/types.go index 8d7d6351e1ec..b4eea52dd0ff 100644 --- a/pkg/image/graph/nodes/types.go +++ b/pkg/image/graph/nodes/types.go @@ -42,6 +42,10 @@ func (n ImageStreamNode) String() string { return string(ImageStreamNodeName(n.ImageStream)) } +func (n ImageStreamNode) ResourceString() string { + return "is/" + n.Name +} + func (*ImageStreamNode) Kind() string { return ImageStreamNodeKind } @@ -79,6 +83,10 @@ func (n ImageStreamTagNode) String() string { return string(ImageStreamTagNodeName(n.ImageStreamTag)) } +func (n ImageStreamTagNode) ResourceString() string { + return "imagestreamtag/" + n.Name +} + func (*ImageStreamTagNode) Kind() string { return ImageStreamTagNodeKind } @@ -148,6 +156,10 @@ func (n ImageNode) String() string { return string(ImageNodeName(n.Image)) } +func (n ImageNode) ResourceString() string { + return "image/" + n.Image.Name +} + func (*ImageNode) Kind() string { return ImageNodeKind }