package models import ( "fmt" "strings" "github.com/BishopFox/cloudfox/aws/graph/ingester/schema" "github.com/BishopFox/cloudfox/internal/aws/policy" "github.com/BishopFox/cloudfox/internal/common" "github.com/dominikbraun/graph" ) type Role struct { Id string AccountID string ARN string Name string TrustsDoc policy.TrustPolicyDocument TrustedPrincipals []TrustedPrincipal TrustedServices []TrustedService TrustedFederatedProviders []TrustedFederatedProvider CanPrivEscToAdmin string IsAdmin string IdValue string IsAdminP bool PathToAdminSameAccount bool PathToAdminCrossAccount bool } type TrustedPrincipal struct { TrustedPrincipal string ExternalID string VendorName string //IsAdmin bool //CanPrivEscToAdmin bool } type TrustedService struct { TrustedService string AccountID string //IsAdmin bool //CanPrivEscToAdmin bool } type TrustedFederatedProvider struct { TrustedFederatedProvider string ProviderShortName string TrustedSubjects string //IsAdmin bool //CanPrivEscToAdmin bool } func (a *Role) MakeRelationships() []schema.Relationship { var relationships []schema.Relationship //instance := singleton.GetInstance() // get thisAccount id from role arn var thisAccount string if len(a.ARN) >= 25 { thisAccount = a.ARN[13:25] } else { fmt.Sprintf("Could not get account number from this role arn%s", a.ARN) } // make a relationship between each role and the account it belongs to relationships = append(relationships, schema.Relationship{ SourceNodeID: a.Id, TargetNodeID: thisAccount, SourceLabel: schema.Role, TargetLabel: schema.Account, RelationshipType: schema.MemberOf, }) for _, TrustedPrincipal := range a.TrustedPrincipals { //get account id from the trusted principal arn var trustedPrincipalAccount string if len(TrustedPrincipal.TrustedPrincipal) >= 25 { trustedPrincipalAccount = TrustedPrincipal.TrustedPrincipal[13:25] } else { fmt.Sprintf("Could not get account number from this TrustedPrincipal%s", TrustedPrincipal.TrustedPrincipal) } var PermissionsRowAccount string // make a TRUSTED_BY relationship between the role and the trusted principal. This does not mean the principal can assume this role, we need more logic to determine that (see below) // relationships = append(relationships, schema.Relationship{ // SourceNodeID: TrustedPrincipal.TrustedPrincipal, // TargetNodeID: a.Id, // SourceLabel: schema.Principal, // TargetLabel: schema.Role, // RelationshipType: schema.IsTrustedBy, // }) // // make a TRUSTS relationship between the trusted principal and this role. This does not mean the principal can assume this role, we need more logic to determine that (see below) // relationships = append(relationships, schema.Relationship{ // SourceNodeID: a.Id, // TargetNodeID: TrustedPrincipal.TrustedPrincipal, // SourceLabel: schema.Role, // TargetLabel: schema.Principal, // RelationshipType: schema.Trusts, // }) // // make a MEMBER_OF relationship between the role and the account // relationships = append(relationships, schema.Relationship{ // SourceNodeID: TrustedPrincipal.TrustedPrincipal, // TargetNodeID: trustedPrincipalAccount, // SourceLabel: schema.Principal, // TargetLabel: schema.Account, // RelationshipType: schema.MemberOf, // }) // if the role trusts a principal in this same account explicitly, then the principal can assume the role if thisAccount == trustedPrincipalAccount { // make a CAN_ASSUME relationship between the trusted principal and this role relationships = append(relationships, schema.Relationship{ SourceNodeID: TrustedPrincipal.TrustedPrincipal, TargetNodeID: a.Id, SourceLabel: schema.Principal, TargetLabel: schema.Role, RelationshipType: schema.CanAssume, }) // make a CAN_BE_ASSUMED_BY relationship between this role and the trusted principal // relationships = append(relationships, schema.Relationship{ // SourceNodeID: a.Id, // TargetNodeID: TrustedPrincipal.TrustedPrincipal, // SourceLabel: schema.Role, // TargetLabel: schema.Principal, // RelationshipType: schema.CanBeAssumedBy, // }) } // If the role trusts a principal in this account or another account using the :root notation, then we need to iterate over all of the rows in AllPermissionsRows to find the principals that have sts:AssumeRole permissions on this role // if the role we are looking at trusts root in it's own account if strings.Contains(TrustedPrincipal.TrustedPrincipal, fmt.Sprintf("%s:root", thisAccount)) { // iterate over all rows in AllPermissionsRows for _, PermissionsRow := range common.PermissionRowsFromAllProfiles { // but we only care about the rows that have arns that are in this account if len(PermissionsRow.Arn) >= 25 { PermissionsRowAccount = PermissionsRow.Arn[13:25] } else { fmt.Sprintf("Could not get account number from this PermissionsRow%s", PermissionsRow.Arn) } if PermissionsRowAccount == thisAccount { // lets only look for rows that have sts:AssumeRole permissions if strings.EqualFold(PermissionsRow.Action, "sts:AssumeRole") || strings.EqualFold(PermissionsRow.Action, "*") || strings.EqualFold(PermissionsRow.Action, "sts:Assume*") || strings.EqualFold(PermissionsRow.Action, "sts:*") { // lets only focus on rows that have an effect of Allow if strings.EqualFold(PermissionsRow.Effect, "Allow") { // if the resource is * or the resource is this role arn, then this principal can assume this role if PermissionsRow.Resource == "*" || strings.Contains(PermissionsRow.Resource, a.ARN) { // make a CAN_ASSUME relationship between the trusted principal and this role //evaluate if the princiapl is a user or a role and set a variable accordingly //var principalType schema.NodeLabel if strings.EqualFold(PermissionsRow.Type, "User") { relationships = append(relationships, schema.Relationship{ SourceNodeID: PermissionsRow.Arn, TargetNodeID: a.Id, SourceLabel: schema.User, TargetLabel: schema.Role, RelationshipType: schema.CanAssume, }) } else if strings.EqualFold(PermissionsRow.Type, "Role") { relationships = append(relationships, schema.Relationship{ SourceNodeID: PermissionsRow.Arn, TargetNodeID: a.Id, SourceLabel: schema.Role, TargetLabel: schema.Role, RelationshipType: schema.CanAssume, }) } // relationships = append(relationships, schema.Relationship{ // SourceNodeID: PermissionsRow.Arn, // TargetNodeID: a.Id, // SourceLabel: principalType, // TargetLabel: schema.Role, // RelationshipType: schema.CanAssumeTest, // }) // make a CAN_BE_ASSUMED_BY relationship between this role and the trusted principal // relationships = append(relationships, schema.Relationship{ // SourceNodeID: a.Id, // TargetNodeID: PermissionsRow.Arn, // SourceLabel: schema.Role, // TargetLabel: schema.Principal, // RelationshipType: schema.CanBeAssumedByTest, // }) } } } } } } else if strings.Contains(TrustedPrincipal.TrustedPrincipal, fmt.Sprintf("%s:root", trustedPrincipalAccount)) { // iterate over all rows in AllPermissionsRows for _, PermissionsRow := range common.PermissionRowsFromAllProfiles { // but we only care about the rows that have arns that are in this other account if len(PermissionsRow.Arn) >= 25 { PermissionsRowAccount = PermissionsRow.Arn[13:25] } else { fmt.Sprintf("Could not get account number from this PermissionsRow%s", PermissionsRow.Arn) } if PermissionsRowAccount == trustedPrincipalAccount { // lets only look for rows that have sts:AssumeRole permissions if strings.EqualFold(PermissionsRow.Action, "sts:AssumeRole") || strings.EqualFold(PermissionsRow.Action, "*") || strings.EqualFold(PermissionsRow.Action, "sts:Assume*") || strings.EqualFold(PermissionsRow.Action, "sts:*") { // lets only focus on rows that have an effect of Allow if strings.EqualFold(PermissionsRow.Effect, "Allow") { // if the resource is * or the resource is this role arn, then this principal can assume this role if PermissionsRow.Resource == "*" || strings.Contains(PermissionsRow.Resource, a.ARN) { // make a CAN_ASSUME relationship between the trusted principal and this role if strings.EqualFold(PermissionsRow.Type, "User") { relationships = append(relationships, schema.Relationship{ SourceNodeID: PermissionsRow.Arn, TargetNodeID: a.Id, SourceLabel: schema.User, TargetLabel: schema.Role, RelationshipType: schema.CanAssumeCrossAccount, }) relationships = append(relationships, schema.Relationship{ SourceNodeID: PermissionsRow.Arn, TargetNodeID: a.Id, SourceLabel: schema.User, TargetLabel: schema.Role, RelationshipType: schema.CanAccess, }) } else if strings.EqualFold(PermissionsRow.Type, "Role") { relationships = append(relationships, schema.Relationship{ SourceNodeID: PermissionsRow.Arn, TargetNodeID: a.Id, SourceLabel: schema.Role, TargetLabel: schema.Role, RelationshipType: schema.CanAssumeCrossAccount, }) relationships = append(relationships, schema.Relationship{ SourceNodeID: PermissionsRow.Arn, TargetNodeID: a.Id, SourceLabel: schema.Role, TargetLabel: schema.Role, RelationshipType: schema.CanAccess, }) } // // make a CAN_BE_ASSUMED_BY relationship between this role and the trusted principal // relationships = append(relationships, schema.Relationship{ // SourceNodeID: a.Id, // TargetNodeID: PermissionsRow.Arn, // SourceLabel: schema.Role, // TargetLabel: schema.Principal, // RelationshipType: schema.CanBeAssumedByTest, // }) } } } } } // If the role trusts :root in another account and the trusted principal is a vendor, we will make a relationship between our role and a vendor node instead of a principal node } else if strings.Contains(TrustedPrincipal.TrustedPrincipal, ":root") && TrustedPrincipal.VendorName != "" { relationships = append(relationships, schema.Relationship{ SourceNodeID: TrustedPrincipal.TrustedPrincipal, TargetNodeID: a.Id, SourceLabel: schema.Vendor, TargetLabel: schema.Role, RelationshipType: schema.CanAssume, }) } } for _, TrustedService := range a.TrustedServices { // make relationship from trusted service to this role of type can assume relationships = append(relationships, schema.Relationship{ SourceNodeID: TrustedService.TrustedService + "_" + TrustedService.AccountID, TargetNodeID: a.Id, SourceLabel: schema.Service, TargetLabel: schema.Role, RelationshipType: schema.IsTrustedBy, }) // make relationship from this role to trusted service of type can be assumed by relationships = append(relationships, schema.Relationship{ SourceNodeID: a.Id, TargetNodeID: TrustedService.TrustedService + "_" + TrustedService.AccountID, SourceLabel: schema.Role, TargetLabel: schema.Service, RelationshipType: schema.Trusts, }) } for _, TrustedFederatedProvider := range a.TrustedFederatedProviders { // make relationship from trusted federated provider to this role of type can assume relationships = append(relationships, schema.Relationship{ SourceNodeID: TrustedFederatedProvider.TrustedFederatedProvider, TargetNodeID: a.Id, SourceLabel: schema.FederatedIdentity, TargetLabel: schema.Role, RelationshipType: schema.CanAssume, }) // make relationship from this role to trusted federated provider of type can be assumed by relationships = append(relationships, schema.Relationship{ SourceNodeID: a.Id, TargetNodeID: TrustedFederatedProvider.TrustedFederatedProvider, SourceLabel: schema.Role, TargetLabel: schema.FederatedIdentity, RelationshipType: schema.CanBeAssumedBy, }) // make relationship from trusted federated provider to this role of type can assume relationships = append(relationships, schema.Relationship{ SourceNodeID: TrustedFederatedProvider.TrustedFederatedProvider, TargetNodeID: a.Id, SourceLabel: schema.FederatedIdentity, TargetLabel: schema.Role, RelationshipType: schema.IsTrustedBy, }) // make relationship from this role to trusted federated provider of type can be assumed by relationships = append(relationships, schema.Relationship{ SourceNodeID: a.Id, TargetNodeID: TrustedFederatedProvider.TrustedFederatedProvider, SourceLabel: schema.Role, TargetLabel: schema.FederatedIdentity, RelationshipType: schema.Trusts, }) } return relationships } // func (a *Node) GenerateAttributes() map[string]string { // attributes := make(map[string]string) // attributes["Id"] = a.Id // attributes["Name"] = a.Name // attributes["Type"] = "Role" // attributes["AccountID"] = a.AccountID // attributes["ARN"] = a.ARN // attributes["CanPrivEscToAdmin"] = a.CanPrivEscToAdmin // attributes["IsAdmin"] = a.IsAdmin // attributes["IdValue"] = a.IdValue // return attributes // } // func (a *Role) MergeAttributes(newAttributes map[string]string) { // if a.Id == "" { // a.Id = newAttributes["Id"] // } // if a.Name == "" { // a.Name = newAttributes["Name"] // } // if a.Type == "" { // a.Type = newAttributes["Type"] // } // if a.AccountID == "" { // a.AccountID = newAttributes["AccountID"] // } // if a.ARN == "" { // a.ARN = newAttributes["ARN"] // } // if a.CanPrivEscToAdmin == "" { // a.CanPrivEscToAdmin = newAttributes["CanPrivEscToAdmin"] // } // if a.IsAdmin == "" { // a.IsAdmin = newAttributes["IsAdmin"] // } // if a.IdValue == "" { // a.IdValue = newAttributes["IdValue"] // } // } func (a *Role) MakeVertices(GlobalGraph graph.Graph[string, string]) { // make a vertex for this role as populate all of the data in the Role struct as attributes err := GlobalGraph.AddVertex( a.Id, graph.VertexAttribute("Name", a.Name), graph.VertexAttribute("Type", "Role"), graph.VertexAttribute("AccountID", a.AccountID), graph.VertexAttribute("ARN", a.ARN), graph.VertexAttribute("CanPrivEscToAdmin", a.CanPrivEscToAdmin), graph.VertexAttribute("IsAdmin", a.IsAdmin), graph.VertexAttribute("IdValue", a.IdValue), ) if err != nil { if err == graph.ErrVertexAlreadyExists { fmt.Println(a.Id + " already exists") } } } // func MakeAllVertices(GlobalRoles []Role, GlobalPmapperGraph aws.PmapperModule) (GlobalGraph graph.Graph[string, string]) { // // for all nodes in the GlobalPmapperGraph, check to see if they exist in the GlobalRoles slice. If they do, then update the node with the new data. If they don't, then add the node to the GlobalRoles slice // for _, node := range GlobalPmapperGraph.Nodes { // // check to see if the node exists in the GlobalRoles slice // var nodeExists bool // for _, role := range GlobalRoles { // if node.Id == role.Id { // nodeExists = true // } // } // // // make a vertex for this role as populate all of the data in the Role struct as attributes // // err := GlobalGraph.AddVertex( // // a.Id, // // graph.VertexAttribute("Name", a.Name), // // graph.VertexAttribute("Type", "Role"), // // graph.VertexAttribute("AccountID", a.AccountID), // // graph.VertexAttribute("ARN", a.ARN), // // graph.VertexAttribute("CanPrivEscToAdmin", a.CanPrivEscToAdmin), // // graph.VertexAttribute("IsAdmin", a.IsAdmin), // // graph.VertexAttribute("IdValue", a.IdValue), // // ) // // if err != nil { // // if err == graph.ErrVertexAlreadyExists { // // fmt.Println(a.Id + " already exists") // // } // // } // } func (a *Role) MakeEdges(GlobalGraph graph.Graph[string, string]) []schema.Relationship { var relationships []schema.Relationship // get thisAccount id from role arn var thisAccount string if len(a.ARN) >= 25 { thisAccount = a.ARN[13:25] } else { fmt.Sprintf("Could not get account number from this role arn%s", a.ARN) } for _, TrustedPrincipal := range a.TrustedPrincipals { //get account id from the trusted principal arn var trustedPrincipalAccount string if len(TrustedPrincipal.TrustedPrincipal) >= 25 { trustedPrincipalAccount = TrustedPrincipal.TrustedPrincipal[13:25] } else { fmt.Sprintf("Could not get account number from this TrustedPrincipal%s", TrustedPrincipal.TrustedPrincipal) } var PermissionsRowAccount string // if the role trusts a principal in this same account explicitly, then the principal can assume the role if thisAccount == trustedPrincipalAccount { // make a CAN_ASSUME relationship between the trusted principal and this role err := GlobalGraph.AddEdge( TrustedPrincipal.TrustedPrincipal, a.Id, graph.EdgeAttribute("AssumeRole", "Same account explicit trust"), ) if err != nil { fmt.Println(err) fmt.Println(TrustedPrincipal.TrustedPrincipal + a.Id + "Same account explicit trust") } } // If the role trusts a principal in this account or another account using the :root notation, then we need to iterate over all of the rows in AllPermissionsRows to find the principals that have sts:AssumeRole permissions on this role // if the role we are looking at trusts root in it's own account if strings.Contains(TrustedPrincipal.TrustedPrincipal, fmt.Sprintf("%s:root", thisAccount)) { err := GlobalGraph.AddVertex( a.Id, graph.VertexAttribute("Name", a.Name), graph.VertexAttribute("Type", "Account"), graph.VertexAttribute("AccountID", a.AccountID), ) if err != nil { if err == graph.ErrVertexAlreadyExists { fmt.Println(a.Id + " already exists") } } // iterate over all rows in AllPermissionsRows for _, PermissionsRow := range common.PermissionRowsFromAllProfiles { // but we only care about the rows that have arns that are in this account if len(PermissionsRow.Arn) >= 25 { PermissionsRowAccount = PermissionsRow.Arn[13:25] } else { fmt.Sprintf("Could not get account number from this PermissionsRow%s", PermissionsRow.Arn) } if PermissionsRowAccount == thisAccount { // lets only look for rows that have sts:AssumeRole permissions if strings.EqualFold(PermissionsRow.Action, "sts:AssumeRole") || strings.EqualFold(PermissionsRow.Action, "*") || strings.EqualFold(PermissionsRow.Action, "sts:Assume*") || strings.EqualFold(PermissionsRow.Action, "sts:*") { // lets only focus on rows that have an effect of Allow if strings.EqualFold(PermissionsRow.Effect, "Allow") { // if the resource is * or the resource is this role arn, then this principal can assume this role if PermissionsRow.Resource == "*" || strings.Contains(PermissionsRow.Resource, a.ARN) { // make a CAN_ASSUME relationship between the trusted principal and this role //evaluate if the princiapl is a user or a role and set a variable accordingly //var principalType schema.NodeLabel if strings.EqualFold(PermissionsRow.Type, "User") { err := GlobalGraph.AddEdge( PermissionsRow.Arn, a.Id, graph.EdgeAttribute("AssumeRole", "Same account root trust and trusted principal has permission to assume role"), ) if err != nil { fmt.Println(err) fmt.Println(PermissionsRow.Arn + a.Id + "Same account root trust and trusted principal has permission to assume role") } } else if strings.EqualFold(PermissionsRow.Type, "Role") { err := GlobalGraph.AddEdge( PermissionsRow.Arn, a.Id, graph.EdgeAttribute("AssumeRole", "Same account root trust and trusted principal has permission to assume role"), ) if err != nil { fmt.Println(err) fmt.Println(PermissionsRow.Arn + a.Id + "Same account root trust and trusted principal has permission to assume role") } } } } } } } } else if strings.Contains(TrustedPrincipal.TrustedPrincipal, ":root") && TrustedPrincipal.VendorName != "" { err := GlobalGraph.AddVertex( a.Id, graph.VertexAttribute("Name", a.Name), graph.VertexAttribute("Type", "Account"), graph.VertexAttribute("AccountID", a.AccountID), graph.VertexAttribute("VendorName", TrustedPrincipal.VendorName), ) if err != nil { if err == graph.ErrVertexAlreadyExists { fmt.Println(a.Id + " already exists") } } // If the role trusts :root in another account and the trusted principal is a vendor, we will make a relationship between our role and a vendor node instead of a principal node relationships = append(relationships, schema.Relationship{ SourceNodeID: TrustedPrincipal.TrustedPrincipal, TargetNodeID: a.Id, SourceLabel: schema.Vendor, TargetLabel: schema.Role, RelationshipType: schema.CanAssume, }) err = GlobalGraph.AddEdge( //TrustedPrincipal.TrustedPrincipal, TrustedPrincipal.VendorName, a.Id, graph.EdgeAttribute("VendorAssumeRole", "Cross account root trust and trusted principal is a vendor"), ) if err != nil { fmt.Println(err) fmt.Println(TrustedPrincipal.VendorName + a.Id + "Cross account root trust and trusted principal is a vendor") } } else if strings.Contains(TrustedPrincipal.TrustedPrincipal, fmt.Sprintf("%s:root", trustedPrincipalAccount)) { err := GlobalGraph.AddVertex( a.Id, graph.VertexAttribute("Name", a.Name), graph.VertexAttribute("Type", "Account"), graph.VertexAttribute("AccountID", a.AccountID), ) if err != nil { if err == graph.ErrVertexAlreadyExists { fmt.Println(a.Id + " already exists") } } // iterate over all rows in AllPermissionsRows for _, PermissionsRow := range common.PermissionRowsFromAllProfiles { // but we only care about the rows that have arns that are in this other account if len(PermissionsRow.Arn) >= 25 { PermissionsRowAccount = PermissionsRow.Arn[13:25] } else { fmt.Sprintf("Could not get account number from this PermissionsRow%s", PermissionsRow.Arn) } if PermissionsRowAccount == trustedPrincipalAccount { // lets only look for rows that have sts:AssumeRole permissions if strings.EqualFold(PermissionsRow.Action, "sts:AssumeRole") || strings.EqualFold(PermissionsRow.Action, "*") || strings.EqualFold(PermissionsRow.Action, "sts:Assume*") || strings.EqualFold(PermissionsRow.Action, "sts:*") { // lets only focus on rows that have an effect of Allow if strings.EqualFold(PermissionsRow.Effect, "Allow") { // if the resource is * or the resource is this role arn, then this principal can assume this role if PermissionsRow.Resource == "*" || strings.Contains(PermissionsRow.Resource, a.ARN) { // make a CAN_ASSUME relationship between the trusted principal and this role if strings.EqualFold(PermissionsRow.Type, "User") { err := GlobalGraph.AddEdge( PermissionsRow.Arn, a.Id, graph.EdgeAttribute("CrossAccountAssumeRole", "Cross account root trust and trusted principal has permission to assume role"), ) if err != nil { fmt.Println(err) fmt.Println(PermissionsRow.Arn + a.Id + "Cross account root trust and trusted principal has permission to assume role") } } else if strings.EqualFold(PermissionsRow.Type, "Role") { err := GlobalGraph.AddEdge( PermissionsRow.Arn, a.Id, graph.EdgeAttribute("CrossAccountAssumeRole", "Cross account root trust and trusted principal has permission to assume role"), ) if err != nil { fmt.Println(err) fmt.Println(PermissionsRow.Arn + a.Id + "Cross account root trust and trusted principal has permission to assume role") } } } } } } } } } // pmapper takes care of this part so commenting out for now - but leaving as a placeholder // for _, TrustedService := range a.TrustedServices { // // make relationship from trusted service to this role of type can assume // // make relationship from this role to trusted service of type can be assumed by // } for _, TrustedFederatedProvider := range a.TrustedFederatedProviders { // make relationship from trusted federated provider to this role of type can assume GlobalGraph.AddVertex( TrustedFederatedProvider.TrustedFederatedProvider, graph.VertexAttribute("Type", "FederatedIdentity"), ) err := GlobalGraph.AddEdge( TrustedFederatedProvider.TrustedFederatedProvider, a.Id, graph.EdgeAttribute("FederatedAssumeRole", "Trusted federated provider"), ) if err != nil { fmt.Println(err) fmt.Println(TrustedFederatedProvider.TrustedFederatedProvider + a.Id + "Trusted federated provider") } } return relationships }