Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 100 additions & 6 deletions aws/resource-trusts.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import (
)

type ResourceTrustsModule struct {
KMSClient sdk.KMSClientInterface
KMSClient *sdk.KMSClientInterface
APIGatewayClient *sdk.APIGatewayClientInterface

// General configuration data
Caller sts.GetCallerIdentityOutput
Expand Down Expand Up @@ -76,10 +77,10 @@ func (m *ResourceTrustsModule) PrintResources(outputDirectory string, verbosity
fmt.Printf("[%s][%s] Enumerating Resources with resource policies for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfileStub), aws.ToString(m.Caller.Account))
// if kms feature flag is enabled include kms in the supported services
if includeKms {
fmt.Printf("[%s][%s] Supported Services: CodeBuild, ECR, EFS, Glue, KMS, Lambda, SecretsManager, S3, SNS, SQS\n",
fmt.Printf("[%s][%s] Supported Services: APIGateway, CodeBuild, ECR, EFS, Glue, KMS, Lambda, SecretsManager, S3, SNS, SQS\n",
cyan(m.output.CallingModule), cyan(m.AWSProfileStub))
} else {
fmt.Printf("[%s][%s] Supported Services: CodeBuild, ECR, EFS, Glue, Lambda, SecretsManager, S3, SNS, "+
fmt.Printf("[%s][%s] Supported Services: APIGateway, CodeBuild, ECR, EFS, Glue, Lambda, SecretsManager, S3, SNS, "+
"SQS (KMS requires --include-kms feature flag)\n",
cyan(m.output.CallingModule), cyan(m.AWSProfileStub))
}
Expand Down Expand Up @@ -244,6 +245,7 @@ func (m *ResourceTrustsModule) executeChecks(r string, wg *sync.WaitGroup, semap
wg.Add(1)
m.getCodeBuildResourcePoliciesPerRegion(r, wg, semaphore, dataReceiver)
}

res, err = servicemap.IsServiceInRegion("lambda", r)
if err != nil {
m.modLog.Error(err)
Expand All @@ -253,6 +255,7 @@ func (m *ResourceTrustsModule) executeChecks(r string, wg *sync.WaitGroup, semap
wg.Add(1)
m.getLambdaPolicyPerRegion(r, wg, semaphore, dataReceiver)
}

res, err = servicemap.IsServiceInRegion("efs", r)
if err != nil {
m.modLog.Error(err)
Expand All @@ -262,6 +265,7 @@ func (m *ResourceTrustsModule) executeChecks(r string, wg *sync.WaitGroup, semap
wg.Add(1)
m.getEFSfilesystemPoliciesPerRegion(r, wg, semaphore, dataReceiver)
}

res, err = servicemap.IsServiceInRegion("secretsmanager", r)
if err != nil {
m.modLog.Error(err)
Expand All @@ -271,6 +275,7 @@ func (m *ResourceTrustsModule) executeChecks(r string, wg *sync.WaitGroup, semap
wg.Add(1)
m.getSecretsManagerSecretsPoliciesPerRegion(r, wg, semaphore, dataReceiver)
}

res, err = servicemap.IsServiceInRegion("glue", r)
if err != nil {
m.modLog.Error(err)
Expand All @@ -281,7 +286,7 @@ func (m *ResourceTrustsModule) executeChecks(r string, wg *sync.WaitGroup, semap
m.getGlueResourcePoliciesPerRegion(r, wg, semaphore, dataReceiver)
}

if includeKms {
if includeKms && m.KMSClient != nil {
res, err = servicemap.IsServiceInRegion("kms", r)
if err != nil {
m.modLog.Error(err)
Expand All @@ -293,6 +298,17 @@ func (m *ResourceTrustsModule) executeChecks(r string, wg *sync.WaitGroup, semap
}
}

if m.APIGatewayClient != nil {
res, err = servicemap.IsServiceInRegion("apigateway", r)
if err != nil {
m.modLog.Error(err)
}
if res {
m.CommandCounter.Total++
wg.Add(1)
m.getAPIGatewayPoliciesPerRegion(r, wg, semaphore, dataReceiver)
}
}
}

func (m *ResourceTrustsModule) Receiver(receiver chan Resource2, receiverDone chan bool) {
Expand Down Expand Up @@ -881,7 +897,7 @@ func (m *ResourceTrustsModule) getKMSPoliciesPerRegion(r string, wg *sync.WaitGr
semaphore <- struct{}{}
defer func() { <-semaphore }()

listKeys, err := sdk.CachedKMSListKeys(m.KMSClient, aws.ToString(m.Caller.Account), r)
listKeys, err := sdk.CachedKMSListKeys(*m.KMSClient, aws.ToString(m.Caller.Account), r)
if err != nil {
sharedLogger.Error(err.Error())
return
Expand All @@ -892,7 +908,7 @@ func (m *ResourceTrustsModule) getKMSPoliciesPerRegion(r string, wg *sync.WaitGr
var statementSummaryInEnglish string
var isInteresting = "No"

keyPolicy, err := sdk.CachedKMSGetKeyPolicy(m.KMSClient, aws.ToString(m.Caller.Account), r, aws.ToString(key.KeyId))
keyPolicy, err := sdk.CachedKMSGetKeyPolicy(*m.KMSClient, aws.ToString(m.Caller.Account), r, aws.ToString(key.KeyId))
if err != nil {
sharedLogger.Error(err.Error())
m.CommandCounter.Error++
Expand Down Expand Up @@ -936,6 +952,84 @@ func (m *ResourceTrustsModule) getKMSPoliciesPerRegion(r string, wg *sync.WaitGr
}
}

func (m *ResourceTrustsModule) getAPIGatewayPoliciesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
defer func() {
m.CommandCounter.Executing--
m.CommandCounter.Complete++
wg.Done()
}()
semaphore <- struct{}{}
defer func() { <-semaphore }()

restAPIs, err := sdk.CachedApiGatewayGetRestAPIs(*m.APIGatewayClient, aws.ToString(m.Caller.Account), r)
if err != nil {
sharedLogger.Error(err.Error())
return
}

for _, restAPI := range restAPIs {

if sdk.IsPublicApiGateway(&restAPI) {
continue
}

var isPublic = "No"
var statementSummaryInEnglish string
var isInteresting = "No"

if restAPI.Policy != nil && *restAPI.Policy != "" {

// remove backslashes from the policy JSON
policyJson := strings.ReplaceAll(aws.ToString(restAPI.Policy), `\"`, `"`)

restAPIPolicy, err := policy.ParseJSONPolicy([]byte(policyJson))
if err != nil {
sharedLogger.Error(fmt.Errorf("parsing policy (%s) as JSON: %s", aws.ToString(restAPI.Name), err))
m.CommandCounter.Error++
continue
}

if !restAPIPolicy.IsEmpty() {
for i, statement := range restAPIPolicy.Statement {
prefix := ""
if len(restAPIPolicy.Statement) > 1 {
prefix = fmt.Sprintf("Statement %d says: ", i)
statementSummaryInEnglish = prefix + statement.GetStatementSummaryInEnglish(*m.Caller.Account) + "\n"
} else {
statementSummaryInEnglish = statement.GetStatementSummaryInEnglish(*m.Caller.Account)
}

statementSummaryInEnglish = strings.TrimSuffix(statementSummaryInEnglish, "\n")
if isResourcePolicyInteresting(statementSummaryInEnglish) {
//magenta(statementSummaryInEnglish)
isInteresting = magenta("Yes")
}

dataReceiver <- Resource2{
AccountID: aws.ToString(m.Caller.Account),
ARN: fmt.Sprintf("arn:aws:execute-api:%s:%s:%s/*", r, *m.Caller.Account, *restAPI.Id),
ResourcePolicySummary: statementSummaryInEnglish,
Public: isPublic,
Name: aws.ToString(restAPI.Name),
Region: r,
Interesting: isInteresting,
}
}
}
} else {
dataReceiver <- Resource2{
AccountID: aws.ToString(m.Caller.Account),
ARN: fmt.Sprintf("arn:aws:execute-api:%s:%s:%s/*", r, *m.Caller.Account, *restAPI.Id),
ResourcePolicySummary: statementSummaryInEnglish,
Public: isPublic,
Name: aws.ToString(restAPI.Name),
Region: r,
Interesting: isInteresting,
}
}
}
}

func (m *ResourceTrustsModule) getGlueResourcePoliciesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
defer func() {
m.CommandCounter.Executing--
Expand Down
65 changes: 62 additions & 3 deletions aws/resource-trusts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ func TestIsResourcePolicyInteresting(t *testing.T) {
}

func TestKMSResourceTrusts(t *testing.T) {

mockedKMSClient := &sdk.MockedKMSClient{}
var kmsClient sdk.KMSClientInterface = mockedKMSClient

testCases := []struct {
outputDirectory string
verbosity int
Expand All @@ -56,8 +60,9 @@ func TestKMSResourceTrusts(t *testing.T) {
outputDirectory: ".",
verbosity: 2,
testModule: ResourceTrustsModule{
KMSClient: &sdk.MockedKMSClient{},
AWSRegions: []string{"us-west-2"},
KMSClient: &kmsClient,
APIGatewayClient: nil,
AWSRegions: []string{"us-west-2"},
Caller: sts.GetCallerIdentityOutput{
Account: aws.String("123456789012"),
Arn: aws.String("arn:aws:iam::123456789012:user/cloudfox_unit_tests"),
Expand All @@ -80,7 +85,61 @@ func TestKMSResourceTrusts(t *testing.T) {
t.Fatal("Resource name does not match expected value")
}
if expectedResource2.ARN != tc.testModule.Resources2[index].ARN {
t.Fatal("Resource ID does not match expected value")
t.Fatal("Resource ARN does not match expected value")
}
}
}
}

func TestAPIGatewayResourceTrusts(t *testing.T) {

mockedAPIGatewayClient := &sdk.MockedAWSAPIGatewayClient{}
var apiGatewayClient sdk.APIGatewayClientInterface = mockedAPIGatewayClient

testCases := []struct {
outputDirectory string
verbosity int
testModule ResourceTrustsModule
expectedResult []Resource2
}{
{
outputDirectory: ".",
verbosity: 2,
testModule: ResourceTrustsModule{
KMSClient: nil,
APIGatewayClient: &apiGatewayClient,
AWSRegions: []string{"us-west-2"},
Caller: sts.GetCallerIdentityOutput{
Account: aws.String("123456789012"),
Arn: aws.String("arn:aws:iam::123456789012:user/cloudfox_unit_tests"),
},
Goroutines: 30,
},
expectedResult: []Resource2{
{
Name: "api1",
ARN: "arn:aws:execute-api:us-west-2:123456789012:abcdefg/*",
Public: "No",
Interesting: "Yes",
},
},
},
}

for _, tc := range testCases {
tc.testModule.PrintResources(tc.outputDirectory, tc.verbosity, false)
for index, expectedResource2 := range tc.expectedResult {
if expectedResource2.Name != tc.testModule.Resources2[index].Name {
t.Fatal("Resource name does not match expected value")
}
if expectedResource2.ARN != tc.testModule.Resources2[index].ARN {
t.Fatal("Resource ARN does not match expected value")
}
if expectedResource2.Public != tc.testModule.Resources2[index].Public {
t.Fatal("Resource Public does not match expected value")
}
if expectedResource2.Interesting != tc.testModule.Resources2[index].Interesting {
t.Fatal("Resource Interesting does not match expected value")
}
}
}
Expand Down
12 changes: 11 additions & 1 deletion aws/sdk/apigateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,17 @@ func init() {
gob.Register([]apiGatewayTypes.UsagePlanKey{})
}

// create a CachedApiGatewayGetRestAPIs function that accepts a client, account id, region. Make sure it handles caching, the region option and pagination
func IsPublicApiGateway(ra *apiGatewayTypes.RestApi) bool {
for _, endpointType := range ra.EndpointConfiguration.Types {
if endpointType == apiGatewayTypes.EndpointTypeRegional || endpointType == apiGatewayTypes.EndpointTypeEdge {
return true
}
}

return false
}

// CachedApiGatewayGetRestAPIs function that accepts a client, account id, region. Make sure it handles caching, the region option and pagination
func CachedApiGatewayGetRestAPIs(client APIGatewayClientInterface, accountID string, region string) ([]apiGatewayTypes.RestApi, error) {
var PaginationControl *string
var restAPIs []apiGatewayTypes.RestApi
Expand Down
1 change: 1 addition & 0 deletions aws/sdk/apigateway_mocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ func (m *MockedAWSAPIGatewayClient) GetRestApis(ctx context.Context, input *apig
apiGatewayTypes.EndpointTypePrivate,
},
},
Policy: aws.String("{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":\"*\",\"Action\":\"execute-api:Invoke\",\"Resource\":\"arn:aws:execute-api:us-west-2:123456789012:abcdefg/*/*/*\"}]}"),
},
{
Id: aws.String("qwerty"),
Expand Down
Loading