Compare commits

...
13 Commits
Author SHA1 Message Date
Bastien Faure f7d1cbcdb6 Merge pull request #96 from BishopFox/aws-principals-with-admin-check
Bug fixes and small feature updates
2024-10-17 10:47:11 +02:00
Seth Art b3c25bb5d0 spelling fix 2024-10-15 16:28:04 -04:00
Seth Art 9076beb111 Bumped version to 1.15.0 2024-10-15 16:26:04 -04:00
Seth Art 878d7ec0c4 Fixed bug in cape where files did not exist crash. Fixed bug that duplicated edges because of magenta. 2024-10-15 16:16:31 -04:00
Seth Art ebf9985d40 Fix for #92 2024-10-10 19:14:09 -04:00
Seth Art 8e1e8d0c0a Fix for cape that also makes edges for cross-account explicit trusts. Also added a new flag to ignore certain edges entirely. 2024-10-10 18:23:54 -04:00
Seth Art a88e98aedd Removed AWSSSO-ACCOUNTID entries from cape table as they are redundant 2024-09-30 16:05:49 -04:00
Seth Art 85b9b11798 Bumped version to 1.43.3 2024-09-24 16:17:49 -04:00
Seth Art 6b0970ebd7 Fix for cape in regards to permission expansion 2024-09-24 16:15:49 -04:00
Seth Art e0dc71b414 updated the adminActionNames check actions to remove ssm get documents and replace with ssm get parameters 2024-09-23 13:48:28 -04:00
Seth Art b31f36746b added the the --admin-check-only flag to iam-simulator command 2024-09-23 13:47:07 -04:00
Seth Art 0a95240b7f added admin/pmapper logic to principals command 2024-09-23 13:45:59 -04:00
David 785b69316a Merge pull request #93 from BishopFox/imds-fix
Fix for IMDS not working due to breaking change in aws-sdk-go-v2.
2024-08-09 00:19:47 -04:00
11 changed files with 244 additions and 45 deletions

No files matched your search

+1 -1
View File
@@ -301,7 +301,7 @@ func preloadData(filePaths []string) (*AllAccountData, error) {
for _, filePath := range filePaths {
fileRecords, err := loadFileRecords(filePath)
if err != nil {
if err != nil {
return nil, err
}
appData.Files[filePath] = fileRecords
+73 -5
View File
@@ -1,7 +1,9 @@
package aws
import (
"bufio"
"fmt"
"os"
"path/filepath"
"strings"
@@ -200,7 +202,12 @@ func (m *CapeCommand) findPathsToThisDestination(allGlobalNodes map[string]map[s
s, sourceVertexWithProperties, _ := m.GlobalGraph.VertexWithProperties(source)
//for the source vertex, we only want to deal with the ones that are NOT in this account
if sourceVertexWithProperties.Attributes["AccountID"] != aws.ToString(m.Caller.Account) {
// skip if the source Name contains AWSSSO-
if strings.Contains(sourceVertexWithProperties.Attributes["Name"], "AWSSSO-") {
continue
}
// now let's see if there is a path from this source to our destination
path, _ := graph.ShortestPath(m.GlobalGraph, s, d)
// if we have a path, then lets document this source as having a path to our destination
if path != nil {
@@ -235,7 +242,7 @@ func (m *CapeCommand) findPathsToThisDestination(allGlobalNodes map[string]map[s
privescPathsBody = append(privescPathsBody, []string{
aws.ToString(m.Caller.Account),
s,
magenta(d),
d,
magenta(destinationVertexWithProperties.Attributes["IsAdminString"]),
paths})
} else {
@@ -289,7 +296,7 @@ func ConvertIAMRoleToNode(role types.Role, vendors *knownawsaccountslookup.Vendo
TrustedPrincipals = append(TrustedPrincipals, TrustedPrincipal{
TrustedPrincipal: principal,
ExternalID: statement.Condition.StringEquals.StsExternalID,
ExternalID: strings.Join(statement.Condition.StringEquals.StsExternalID, "\n"),
VendorName: vendorName,
//IsAdmin: false,
//CanPrivEscToAdmin: false,
@@ -650,6 +657,48 @@ func (a *Node) MakeRoleEdges(GlobalGraph graph.Graph[string, string]) {
}
}
// if the role trusts a principal in another 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.Arn,
//graph.EdgeAttribute("AssumeRole", "Cross account explicit trust"),
graph.EdgeAttribute("AssumeRole", "can assume (because of an explicit cross account trust) "),
)
if err != nil {
//fmt.Println(err)
//fmt.Println(TrustedPrincipal.TrustedPrincipal + a.Arn + "Cross account explicit trust")
if err == graph.ErrEdgeAlreadyExists {
// update the edge by copying the existing graph.Edge with attributes and add the new attributes
//fmt.Println("Edge already exists")
// get the existing edge
existingEdge, _ := GlobalGraph.Edge(TrustedPrincipal.TrustedPrincipal, a.Arn)
// get the map of attributes
existingProperties := existingEdge.Properties
// add the new attributes to attributes map within the properties struct
// Check if the Attributes map is initialized, if not, initialize it
if existingProperties.Attributes == nil {
existingProperties.Attributes = make(map[string]string)
}
// Add or update the attribute
existingProperties.Attributes["AssumeRole"] = "can assume (because of an explicit cross account trust) "
err = GlobalGraph.UpdateEdge(
TrustedPrincipal.TrustedPrincipal,
a.Arn,
graph.EdgeAttributes(existingProperties.Attributes),
)
if err != nil {
fmt.Println(err)
}
}
}
}
// 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
@@ -667,6 +716,7 @@ func (a *Node) MakeRoleEdges(GlobalGraph graph.Graph[string, string]) {
if PermissionsRowAccount == thisAccount {
// lets only look for rows that have sts:AssumeRole permissions
if policy.MatchesAfterExpansion(PermissionsRow.Action, "sts:AssumeRole") {
// 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
@@ -820,10 +870,10 @@ func (a *Node) MakeRoleEdges(GlobalGraph graph.Graph[string, string]) {
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 policy.MatchesAfterExpansion(PermissionsRow.Action, "sts:AssumeRole") {
// lets only look for rows that have sts:AssumeRole permis sions
if policy.MatchesAfterExpansion("sts:AssumeRole", PermissionsRow.Action) {
// if strings.EqualFold(PermissionsRow.Action, "sts:AssumeRole") ||
// strings.EqualFold(PermissionsRow.Action, "*") ||
// 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
@@ -979,3 +1029,21 @@ func (a *Node) MakeRoleEdges(GlobalGraph graph.Graph[string, string]) {
}
}
// function to read file specified in CapeArnIgnoreList which is separated by newlines, and convert it to a slice of strings with each line as an entry in the slice.
// the function accepts a string with the filename
func ReadArnIgnoreListFile(filename string) ([]string, error) {
var arnIgnoreList []string
file, err := os.Open(filename)
if err != nil {
return arnIgnoreList, err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
arnIgnoreList = append(arnIgnoreList, scanner.Text())
}
return arnIgnoreList, scanner.Err()
}
+1 -1
View File
@@ -331,7 +331,7 @@ func (m *GraphCommand) collectRoleDataForGraph() []models.Role {
TrustedPrincipals = append(TrustedPrincipals, models.TrustedPrincipal{
TrustedPrincipal: principal,
ExternalID: statement.Condition.StringEquals.StsExternalID,
ExternalID: strings.Join(statement.Condition.StringEquals.StsExternalID, "\n"),
VendorName: vendorName,
//IsAdmin: false,
//CanPrivEscToAdmin: false,
+15 -5
View File
@@ -33,8 +33,9 @@ type IamSimulatorModule struct {
WrapTable bool
// Main module data
SimulatorResults []SimulatorResult
CommandCounter internal.CommandCounter
SimulatorResults []SimulatorResult
CommandCounter internal.CommandCounter
IamSimulatorAdminCheckOnly bool
// Used to store output data for pretty printing
output internal.OutputData2
modLog *logrus.Entry
@@ -104,6 +105,11 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string,
go m.Receiver(dataReceiver, receiverDone)
if m.IamSimulatorAdminCheckOnly {
// set defaultActionNames to an empty slice
defaultActionNames = []string{}
}
// This double if/else section is here to handle the cases where --principal or --action (or both) are specified.
if principal != "" {
if action != "" {
@@ -354,7 +360,9 @@ func (m *IamSimulatorModule) getIAMUsers(wg *sync.WaitGroup, actions []string, r
Decision: "",
}
} else {
m.getPolicySimulatorResult(principal, actions, resource, dataReceiver)
if !m.IamSimulatorAdminCheckOnly {
m.getPolicySimulatorResult(principal, actions, resource, dataReceiver)
}
}
}
@@ -394,7 +402,9 @@ func (m *IamSimulatorModule) getIAMRoles(wg *sync.WaitGroup, actions []string, r
Decision: "",
}
} else {
m.getPolicySimulatorResult(principal, actions, resource, dataReceiver)
if !m.IamSimulatorAdminCheckOnly {
m.getPolicySimulatorResult(principal, actions, resource, dataReceiver)
}
}
}
@@ -443,7 +453,7 @@ func (m *IamSimulatorModule) isPrincipalAnAdmin(principal *string) bool {
"iam:PutRolePolicy",
"iam:AttachRolePolicy",
"secretsmanager:GetSecretValue",
"ssm:GetDocument",
"ssm:GetParameters",
}
for {
SimulatePrincipalPolicy, err := m.IAMClient.SimulatePrincipalPolicy(
+41
View File
@@ -26,6 +26,12 @@ type IamPrincipalsModule struct {
AWSProfile string
WrapTable bool
SkipAdminCheck bool
iamSimClient IamSimulatorModule
pmapperMod PmapperModule
pmapperError error
PmapperDataBasePath string
// Main module data
Users []User
Roles []Role
@@ -43,6 +49,8 @@ type User struct {
Name string
AttachedPolicies []string
InlinePolicies []string
Admin string
CanPrivEsc string
}
type Group struct {
@@ -62,6 +70,8 @@ type Role struct {
Name string
AttachedPolicies []string
InlinePolicies []string
Admin string
CanPrivEsc string
}
func (m *IamPrincipalsModule) PrintIamPrincipals(outputDirectory string, verbosity int) {
@@ -69,6 +79,7 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputDirectory string, verbosi
m.output.Verbosity = verbosity
m.output.Directory = outputDirectory
m.output.CallingModule = "principals"
localAdminMap := make(map[string]bool)
m.modLog = internal.TxtLog.WithFields(logrus.Fields{
"module": m.output.CallingModule,
})
@@ -78,6 +89,9 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputDirectory string, verbosi
fmt.Printf("[%s][%s] Enumerating IAM Users and Roles for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
m.pmapperMod, m.pmapperError = InitPmapperGraph(m.Caller, m.AWSProfile, m.Goroutines, m.PmapperDataBasePath)
m.iamSimClient = InitIamCommandClient(m.IAMClient, m.Caller, m.AWSProfile, m.Goroutines)
// wg := new(sync.WaitGroup)
// done := make(chan bool)
@@ -101,6 +115,8 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputDirectory string, verbosi
"Arn",
"AttachedPolicies",
"InlinePolicies",
"IsAdminRole?",
"CanPrivEscToAdmin?",
}
// If the user specified table columns, use those.
@@ -122,6 +138,8 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputDirectory string, verbosi
"Arn",
//"AttachedPolicies",
//"InlinePolicies",
"IsAdminRole?",
"CanPrivEscToAdmin?",
}
// Otherwise, use the default columns.
@@ -132,11 +150,25 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputDirectory string, verbosi
"Arn",
// "AttachedPolicies",
// "InlinePolicies",
"IsAdminRole?",
"CanPrivEscToAdmin?",
}
}
// Remove the pmapper row if there is no pmapper data
if m.pmapperError != nil {
sharedLogger.Errorf("%s - %s - No pmapper data found for this account. Skipping the pmapper column in the output table.", m.output.CallingModule, m.AWSProfile)
tableCols = removeStringFromSlice(tableCols, "CanPrivEscToAdmin?")
}
//Table rows
for i := range m.Users {
if m.pmapperError == nil {
m.Users[i].Admin, m.Users[i].CanPrivEsc = GetPmapperResults(m.SkipAdminCheck, m.pmapperMod, &m.Users[i].Arn)
} else {
m.Users[i].Admin, m.Users[i].CanPrivEsc = GetIamSimResult(m.SkipAdminCheck, &m.Users[i].Arn, m.iamSimClient, localAdminMap)
}
m.output.Body = append(
m.output.Body,
[]string{
@@ -146,12 +178,19 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputDirectory string, verbosi
m.Users[i].Arn,
strings.Join(m.Users[i].AttachedPolicies, " , "),
strings.Join(m.Users[i].InlinePolicies, " , "),
m.Users[i].Admin,
m.Users[i].CanPrivEsc,
},
)
}
for i := range m.Roles {
if m.pmapperError == nil {
m.Roles[i].Admin, m.Roles[i].CanPrivEsc = GetPmapperResults(m.SkipAdminCheck, m.pmapperMod, &m.Roles[i].Arn)
} else {
m.Roles[i].Admin, m.Roles[i].CanPrivEsc = GetIamSimResult(m.SkipAdminCheck, &m.Roles[i].Arn, m.iamSimClient, localAdminMap)
}
m.output.Body = append(
m.output.Body,
[]string{
@@ -161,6 +200,8 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputDirectory string, verbosi
m.Roles[i].Arn,
strings.Join(m.Roles[i].AttachedPolicies, " , "),
strings.Join(m.Roles[i].InlinePolicies, " , "),
m.Roles[i].Admin,
m.Roles[i].CanPrivEsc,
},
)
+6 -5
View File
@@ -219,9 +219,10 @@ func (m *RoleTrustsModule) printPrincipalTrusts(outputDirectory string) ([]strin
RoleARN: aws.ToString(role.roleARN),
RoleName: GetResourceNameFromArn(aws.ToString(role.roleARN)),
TrustedPrincipal: principal,
ExternalID: statement.Condition.StringEquals.StsExternalID,
IsAdmin: role.Admin,
CanPrivEsc: role.CanPrivEsc,
// if there is more than one externalID concat them using newlines
ExternalID: strings.Join(statement.Condition.StringEquals.StsExternalID, "\n"),
IsAdmin: role.Admin,
CanPrivEsc: role.CanPrivEsc,
}
body = append(body, []string{
aws.ToString(m.Caller.Account),
@@ -281,7 +282,7 @@ func (m *RoleTrustsModule) printPrincipalTrustsRootOnly(outputDirectory string)
for _, role := range m.AnalyzedRoles {
for _, statement := range role.trustsDoc.Statement {
for _, principal := range statement.Principal.AWS {
if strings.Contains(principal, ":root") && statement.Condition.StringEquals.StsExternalID == "" {
if strings.Contains(principal, ":root") && statement.Condition.StringEquals.StsExternalID == nil {
accountID := strings.Split(principal, ":")[4]
vendorName := m.vendors.GetVendorNameFromAccountID(accountID)
if vendorName != "" {
@@ -292,7 +293,7 @@ func (m *RoleTrustsModule) printPrincipalTrustsRootOnly(outputDirectory string)
RoleARN: aws.ToString(role.roleARN),
RoleName: GetResourceNameFromArn(aws.ToString(role.roleARN)),
TrustedPrincipal: principal,
ExternalID: statement.Condition.StringEquals.StsExternalID,
ExternalID: strings.Join(statement.Condition.StringEquals.StsExternalID, "\n"),
IsAdmin: role.Admin,
CanPrivEsc: role.CanPrivEsc,
}
+54 -25
View File
@@ -138,9 +138,10 @@ var (
PostRun: awsPostRun,
}
CapeAdminOnly bool
CapeJobName string
CapeCommand = &cobra.Command{
CapeAdminOnly bool
CapeArnIgnoreList string
CapeJobName string
CapeCommand = &cobra.Command{
Use: "cape",
Aliases: []string{"CAPE"},
Short: "Cross-Account Privilege Escalation Route finder. Needs to be run with multiple profiles using -l or -a flag. Needs pmapper data to be present",
@@ -263,10 +264,11 @@ var (
PostRun: awsPostRun,
}
SimulatorResource string
SimulatorAction string
SimulatorPrincipal string
IamSimulatorCommand = &cobra.Command{
SimulatorResource string
SimulatorAction string
SimulatorPrincipal string
IamSimulatorAdminCheckOnly bool
IamSimulatorCommand = &cobra.Command{
Use: "iam-simulator",
Aliases: []string{"iamsimulator", "simulator"},
Short: "Wrapper around the AWS IAM Simulate Principal Policy command",
@@ -1172,6 +1174,20 @@ func runCapeCommand(cmd *cobra.Command, args []string) {
}
}
// if the CapeArnIgnoreList arg is not empty, read the file and add the arns to the CapeArnIgnoreList
if CapeArnIgnoreList != "" {
// call ReadArnIgnoreListFile and add the arns to the CapeArnIgnoreList
arnsToIgnore, err := aws.ReadArnIgnoreListFile(CapeArnIgnoreList)
if err != nil {
fmt.Println("Error reading the arn ignore list file: " + err.Error())
}
// remove nodes that are in the CapeArnIgnoreList from the graph
for _, arn := range arnsToIgnore {
GlobalGraph.RemoveVertex(arn)
}
}
// make pmapper edges
//you can update edges, so we can just merge attributes as needed
// first we add the edges that already exist in pmapper, then later we will make more edges based on the cloudfox role trusts logic
@@ -1318,10 +1334,20 @@ func runCapeTUICommand(cmd *cobra.Command, args []string) {
if _, err := os.Stat(filepath.Join(cloudfoxRunData.OutputLocation, "json", fileName)); os.IsNotExist(err) {
fmt.Printf("[%s] Could not retrieve CAPE data for profile %s.\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)), profile)
//remove the profile from the list of profiles to analyze
AWSProfiles = append(AWSProfiles[:i], AWSProfiles[i+1:]...)
if len(AWSProfiles) > 1 {
AWSProfiles = append(AWSProfiles[:i], AWSProfiles[i+1:]...)
} else {
if CapeAdminOnly {
fmt.Printf("[%s] Could not retrieve cape data. Did you run cape without the --admin-only flag? You'll need to run cape with --admin-only to use the tui with --admin-only\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)))
} else {
fmt.Printf("[%s] Did you run cape with the --admin-only flag? You'll need to run cape without --admin-only to use the tui without --admin-only\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)))
}
os.Exit(1)
}
}
}
if len(capeOutputFileLocations) == 0 {
fmt.Printf("[%s] Could not retrieve CAPE data.\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", cmd.Root().Version)))
os.Exit(1)
@@ -1338,13 +1364,14 @@ func runIamSimulatorCommand(cmd *cobra.Command, args []string) {
continue
}
m := aws.IamSimulatorModule{
IAMClient: iam.NewFromConfig(AWSConfig),
Caller: *caller,
AWSProfileProvided: profile,
Goroutines: Goroutines,
WrapTable: AWSWrapTable,
AWSOutputType: AWSOutputType,
AWSTableCols: AWSTableCols,
IAMClient: iam.NewFromConfig(AWSConfig),
Caller: *caller,
AWSProfileProvided: profile,
Goroutines: Goroutines,
WrapTable: AWSWrapTable,
AWSOutputType: AWSOutputType,
AWSTableCols: AWSTableCols,
IamSimulatorAdminCheckOnly: IamSimulatorAdminCheckOnly,
}
m.PrintIamSimulator(SimulatorPrincipal, SimulatorAction, SimulatorResource, AWSOutputDirectory, Verbosity)
}
@@ -1547,13 +1574,15 @@ func runPrincipalsCommand(cmd *cobra.Command, args []string) {
continue
}
m := aws.IamPrincipalsModule{
IAMClient: iam.NewFromConfig(AWSConfig),
Caller: *caller,
AWSProfile: profile,
Goroutines: Goroutines,
WrapTable: AWSWrapTable,
AWSOutputType: AWSOutputType,
AWSTableCols: AWSTableCols,
IAMClient: iam.NewFromConfig(AWSConfig),
Caller: *caller,
AWSProfile: profile,
Goroutines: Goroutines,
SkipAdminCheck: AWSSkipAdminCheck,
WrapTable: AWSWrapTable,
AWSOutputType: AWSOutputType,
AWSTableCols: AWSTableCols,
PmapperDataBasePath: PmapperDataBasePath,
}
m.PrintIamPrincipals(AWSOutputDirectory, Verbosity)
}
@@ -2375,9 +2404,6 @@ func init() {
// Map Access Keys Module Flags
AccessKeysCommand.Flags().StringVarP(&AccessKeysFilter, "filter", "f", "none", "Access key ID to search for")
// IAM Simulator Module Flags
//IamSimulatorCommand.Flags().StringVarP(&IamSimulatorFilter, "filter", "f", "none", "Access key ID to search for")
// Instances Map Module Flags
InstancesCommand.Flags().StringVarP(&InstancesFilter, "filter", "f", "all", "[InstanceID | InstanceIDsFile]")
InstancesCommand.Flags().BoolVarP(&InstanceMapUserDataAttributesOnly, "userdata", "u", false, "Use this flag to retrieve only the userData attribute from EC2 instances.")
@@ -2395,6 +2421,7 @@ func init() {
IamSimulatorCommand.Flags().StringVar(&SimulatorPrincipal, "principal", "", "Principal Arn")
IamSimulatorCommand.Flags().StringVar(&SimulatorAction, "action", "", "Action")
IamSimulatorCommand.Flags().StringVar(&SimulatorResource, "resource", "*", "Resource")
IamSimulatorCommand.Flags().BoolVar(&IamSimulatorAdminCheckOnly, "admin-check-only", false, "Only check check if principals are admin")
// iam-simulator module flags
PermissionsCommand.Flags().StringVar(&PermissionsPrincipal, "principal", "", "Principal Arn")
@@ -2408,6 +2435,8 @@ func init() {
// cape command flags
CapeCommand.Flags().BoolVar(&CapeAdminOnly, "admin-only", false, "Only return paths that lead to an admin role - much faster")
//CapeCommand.Flags().StringVar(&CapeJobName, "job-name", "", "Name of the cape job")
// flag that accepts a list of arns to ignore
CapeCommand.Flags().StringVar(&CapeArnIgnoreList, "arn-ignore-list", "", "File containing a list of ARNs to ignore separated by newlines")
// cape tui command flags
CapeTuiCmd.Flags().BoolVar(&CapeAdminOnly, "admin-only", false, "Only return paths that lead to an admin role - much faster")
+1 -2
View File
@@ -4,5 +4,4 @@ const CLOUDFOX_USER_AGENT = "cloudfox"
const CLOUDFOX_LOG_FILE_DIR_NAME = ".cloudfox"
const CLOUDFOX_BASE_DIRECTORY = "cloudfox-output"
const LOOT_DIRECTORY_NAME = "loot"
const CLOUDFOX_VERSION = "1.14.2"
const CLOUDFOX_VERSION = "1.15.0"
+3
View File
@@ -109,6 +109,9 @@ func MatchesAfterExpansion(stringFromPolicyToCheck, stringToCheckAgainst string)
return pattern.MatchString(stringFromPolicyToCheck)
}
func (p *Policy) DoesPolicyHaveMatchingStatement(effect string, actionToCheck string, resourceToCheck string) bool {
for _, statement := range p.Statement {
+48
View File
@@ -222,3 +222,51 @@ func TestDoesPolicyHaveMatchingStatement(t *testing.T) {
}
}
}
func TestDoesPermissionExpansionMatch(t *testing.T) {
tests := []struct {
actionToExpand string
actionToCheckAgainst string
want bool
}{
{
actionToExpand: "sts:AssumeRole",
actionToCheckAgainst: "sts:AssumeRole",
want: true,
},
{
actionToExpand: "*",
actionToCheckAgainst: "sts:AssumeRole",
want: true,
},
{
actionToExpand: "sts:Assume*",
actionToCheckAgainst: "sts:AssumeRole",
want: true,
},
{
actionToExpand: "sts:*",
actionToCheckAgainst: "sts:AssumeRole",
want: true,
},
{
actionToExpand: "s3:GetObject",
actionToCheckAgainst: "sts:AssumeRole",
want: false,
},
{
actionToExpand: "s3:*",
actionToCheckAgainst: "sts:AssumeRole",
want: false,
},
}
for _, tt := range tests {
actual := MatchesAfterExpansion(tt.actionToCheckAgainst, tt.actionToExpand)
if tt.want != actual {
//fmt.Printf("DoesPermissionHaveMatchingStatement(%s, %s) is %v but should be %v", tt.actionToExpand, tt.actionToCheckAgainst, actual, tt.want)
t.Errorf("DoesPermissionHaveMatchingStatement(%s, %s) is %v but should be %v", tt.actionToExpand, tt.actionToCheckAgainst, actual, tt.want)
}
}
}
+1 -1
View File
@@ -26,7 +26,7 @@ type RoleTrustStatementEntry struct {
Action string `json:"Action"`
Condition struct {
StringEquals struct {
StsExternalID string `json:"sts:ExternalId"`
StsExternalID ListOfPrincipals `json:"sts:ExternalId"`
SAMLAud string `json:"SAML:aud"`
TokenActionsGithubusercontentComSub ListOfPrincipals `json:"token.actions.githubusercontent.com:sub"`
TokenActionsGithubusercontentComAud string `json:"token.actions.githubusercontent.com:aud"`