On hierarchical identities
What something is does not imply where something is.
Names in computing systems generally follow some sort of hierarchical pattern. Domain names, filesystem paths, and IP addresses all describe tree structures, the leaves of which contain some thing we want to identify. These trees allow for different parties to make decisions about groups of named items in a single step, vastly simplifying the work required to do things like issue certificates or route network traffic. Given this, it may be tempting to design identities in an IAM system to have a similar hierarchy, such as the X.500 directory. This is frequently a bad idea.

To understand why hierarchical naming schemes in IAM typically do not work well in practice, consider the following illustrative real-world example. A SPIFFE deployment contains multiple Kubernetes clusters in a single trust domain. (This is an atypical setup, but one which I have seen organizations use to minimize trust bundle federation.) This deployment has a workload with a SPIFFE ID describing a service account called worker in a Kubernetes application called my-application, deployed in some cluster c-100. Such an ID might look like spiffe://td-prod/c/c-100/ns/my-application/sa/worker. This URI adheres to a fairly typical convention seen in technologies that use SPIFFE such as SPIRE and Istio, where paths contain attribute names like sa (for service account) followed by the attribute value. These attributes are then arranged in some hierarchy. In theory, building an authorization policy based on such an ID format should be straightforward; a policy author can simply choose the granularity of identities they want to allow by choosing an appropriate level of a hierarchy to authorize. In practice, however, the situation is frequently much more complicated.
Assume that this worker service account in the my-application application needs access to a shared resource, such as a database. This resource is shared across clusters, all of which should be able to access the resource. How does a policy author grant all instances of the worker service account access to the resource? There are multiple approaches to solving this problem. One is to enumerate all clusters with the my-application application, then update an ACL or other policy to grant each individual workload access to the resource. If there are three clusters c-100, c-200, and c-300, each of which contain this application, an ACL might contain the following SPIFFE IDs:
spiffe://td-prod/c/c-100/ns/my-application/sa/workerspiffe://td-prod/c/c-200/ns/my-application/sa/workerspiffe://td-prod/c/c-300/ns/my-application/sa/worker
This has the advantage of explicitly listing all identities that can access the resource, eliminating the need for interpretation of the SPIFFE ID itself, but requires centralized policy updates whenever the my-application application is deployed on another cluster in the td-prod trust domain.
Another approach is to remove cluster scoping from the SPIFFE ID entirely, which simplifies policy management at the cost of removing information about a given identity. While this would reduce our ACL to a single entry for this use case, it would also prevent us from being able to add authorization policies regarding in-cluster or cross-cluster communication.
As a third option, it is possible to define sets of identities that can access the resource based on the SPIFFE ID. In some use cases, this might be a simple prefix match (e.g., granting all workloads in a cluster access to a resource by matching on IDs starting with spiffe://td-prod/c/c-100/). For our example, however, this will not work, as we are actually interested in the suffix /ns/my-application/sa/worker at the end of each SPIFFE ID. Defining the exact semantics for how to match on portions of a SPIFFE ID path can be challenging, as the meaning of a given path is out of the scope of the SPIFFE ID standard. Thus, depending on the requirements of a given SPIFFE deployment, authorization policy implementation may take many forms, from wildcards on path segments to regular expressions or other forms of substring matching.
In all three scenarios, we must either remove information from or add complexity to our IAM system to ensure an application can access a resource. A seemingly elegant approach to building a tree of identities now requires complex workarounds to define fairly trivial authorization policies.
This is a common result of imposing hierarchical relationships in domains that do not have a single natural hierarchy. Fortunately, there are other approaches one can take to building identities. Rather than worry about the "right" hierarchy to choose for a particular use case, one alternative is to consider all identities as collections of attributes. These can then map to various outputs, such as SPIFFE IDs or JWT claims. Framed in this way, SPIFFE IDs can encode attributes as an associative list of name/value pairs within a URI, where those attributes may or may not share a hierarchical relationship. The SPIFFE ID spiffe://td-prod/c/c-100/ns/my-application/sa/worker then becomes something closer to a set of claims than a path through some tree.
This model lends itself well to other models of authorization, such as ABAC. AWS, for example, has support for policy variables that one can extract from policy inputs like OIDC ID tokens or session tags to make authorization decisions. Similarly, OpenFGA has support for contextual tuples, a method for introducing dynamic information into an authorization check that users can employ to inject attributes into the access control context. Rather than rely on hierarchical relationships within a given identity to make policy decisions, these systems treat identities as a set of claims about some party, which is frequently a more natural way to express both identities and authorization policies.
While identities can be arranged in a hierarchical fashion, there is rarely one hierarchy that fits all use cases. A user may be "in" a group, or a service may be "in" a given location, but for the purpose of policy management, the only input a policy really needs is the attributes of a principal trying to do something. By building around properties of an identity rather than that identity's position in a tree, it is possible to develop authorization systems that are simpler, more expressive, and more reliable for users.