From 452b590d5b79f06209803cf374384008f6eda2d5 Mon Sep 17 00:00:00 2001 From: Jesse Taylor Date: Mon, 31 Aug 2020 12:49:39 -0700 Subject: [PATCH 1/9] working on term. environment and app removal --- .../aws_elasticbeanstalk_resource.go | 244 ++++++++++++++++++ cloud-inquisitor/resource.go | 25 ++ .../elastic_beanstalk_dns_hijacks.json | 17 ++ terraform_modules/project_role/iam.tf | 3 +- 4 files changed, 288 insertions(+), 1 deletion(-) create mode 100644 cloud-inquisitor/aws_elasticbeanstalk_resource.go create mode 100644 event_rules/elastic_beanstalk_dns_hijacks.json diff --git a/cloud-inquisitor/aws_elasticbeanstalk_resource.go b/cloud-inquisitor/aws_elasticbeanstalk_resource.go new file mode 100644 index 0000000..a5f2bc8 --- /dev/null +++ b/cloud-inquisitor/aws_elasticbeanstalk_resource.go @@ -0,0 +1,244 @@ +package cloudinquisitor + +import ( + "context" + "encoding/json" + "errors" + "reflect" + + instrument "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/instrumentation" + log "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/logger" + "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/settings" + "github.com/aws/aws-lambda-go/events" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/elasticbeanstalk" + "github.com/sirupsen/logrus" +) + +type AWSElasticBeanstalkEnvironmentResource struct { + AccountID string + ApplicationName string + EnvironmentName string + EnvironmentId string + Region string + CNAME string + Endpoint string + Status string + logger *log.Logger +} + +type AWSElasticBeanstalkDetail struct { + EventName string `json:"eventName"` + ErrorCode string `json:"errorCode"` + ErrorMessage string `json:"errorMessage"` + AWSRegion string `json:"awsRegion"` + RequestParamaters AWSElasticBeastalkRequestParameters `json:"requestParameters"` + ResponseElements AWSElasticBeanstalkResponseElement `json:"responseElements"` +} + +type AWSElasticBeastalkRequestParameters struct { + EnvironmentId string `json:"environmentId"` +} +type AWSElasticBeanstalkResponseElement struct { + /*Distribution struct { + ID string `json:"id"` + DomainName string `json:"domainName"` + Status string `json:"status"` + DistributionConfig struct { + Origins struct { + Items []struct { + DomainName string `json:"domainName"` + ID string `json:"id"` + } `json:"items"` + } `json:"origins"` + OriginGroups struct { + Items []struct { + ID string `json:"id"` + Members struct { + Items []struct { + OriginID string `json:"originId"` + } `json:"items"` + } `json:"members"` + } `json:"items"` + } `json:"originGroups"` + } `json:"distributionConfig"` + } `json:"distribution"`*/ +} + +func (eb *AWSElasticBeanstalkEnvironmentResource) RefreshState() error { + if !settings.IsSet("assume_role") { + eb.logger.WithFields(logrus.Fields{ + "cloud-inquisitor-resource": "aws-elasticbeanstalk-environment", + "cloud-inquisitor-error": "missing settings value", + }).WithFields(eb.GetMetadata()).Error(errors.New("settings value assume_role not set")) + return errors.New("settings value assume_role not set") + } + accountSession, err := AWSAssumeRole(eb.AccountID, settings.GetString("assume_role"), session.New()) + regionalSession := accountSession.Copy(&aws.Config{ + Region: aws.String(eb.Region), + }) + if err != nil { + eb.logger.WithFields(logrus.Fields{ + "cloud-inquisitor-resource": "aws-elasticbeanstalk-environment", + "cloud-inquisitor-error": "assume role error", + }).WithFields(eb.GetMetadata()).Error(err.Error()) + return err + } + + svc := elasticbeanstalk.New(regionalSession) + input := &elasticbeanstalk.DescribeEnvironmentsInput{ + EnvironmentIds: []*string{ + aws.String(eb.EnvironmentId), + }, + IncludeDeleted: aws.Bool(true), + } + + result, err := svc.DescribeEnvironments(input) + if err != nil { + eb.logger.WithFields(logrus.Fields{ + "cloud-inquisitor-resource": "aws-elasticbeanstalk-environment", + "cloud-inquisitor-error": "DescribeEnvironments", + }).WithFields(eb.GetMetadata()).Error(err.Error()) + return err + } + + var foundEnv *elasticbeanstalk.EnvironmentDescription = nil + for _, env := range result.Environments { + if *env.EnvironmentId == eb.EnvironmentId { + foundEnv = env + break + } + } + + eb.Status = *foundEnv.Status + eb.ApplicationName = *foundEnv.ApplicationName + eb.CNAME = *foundEnv.CNAME + eb.Endpoint = *foundEnv.EndpointURL + eb.EnvironmentName = *foundEnv.EnvironmentName + + return nil +} + +func (eb *AWSElasticBeanstalkEnvironmentResource) SendNotification() error { + return nil +} + +func (eb *AWSElasticBeanstalkEnvironmentResource) GetType() string { + return SERVICE_AWS_ELASTICBEANSTALK +} + +func (eb *AWSElasticBeanstalkEnvironmentResource) GetMetadata() map[string]interface{} { + return map[string]interface{}{ + "account": eb.AccountID, + "application-name": eb.ApplicationName, + "enviornment-name": eb.EnvironmentName, + "environment-id": eb.EnvironmentId, + "cname": eb.CNAME, + "endpoint": eb.Endpoint, + "status": eb.Status, + "serviceType": eb.GetType(), + } +} + +func (eb *AWSElasticBeanstalkEnvironmentResource) GetLogger() *log.Logger { + return eb.logger +} + +type AWSElasticBeanstalkEnvironmentHijackableResource struct { + AWSElasticBeanstalkEnvironmentResource +} + +func (eb *AWSElasticBeanstalkEnvironmentHijackableResource) NewFromEventBus(event events.CloudWatchEvent, ctx context.Context, passedInMetadata map[string]interface{}) error { + defaultMetadata, err := DefaultLambdaMetadata(ctx) + if err != nil { + return err + } + + mergedMetaData := map[string]interface{}{} + for k, v := range passedInMetadata { + mergedMetaData[k] = v + } + for k, v := range defaultMetadata { + mergedMetaData[k] = v + } + + opts := log.LoggerOpts{ + Level: log.LogrusLevelConv(settings.GetString("log_level")), + Metadata: mergedMetaData, + } + logger := instrument.GetInstrumentedLogger(opts, ctx) + eb.logger = logger + + var ebDetails AWSElasticBeanstalkDetail + err = json.Unmarshal(event.Detail, &ebDetails) + if err != nil { + eb.logger.Error(err.Error(), nil) + return err + } + + if ebDetails.ErrorCode != "" { + return errors.New(ebDetails.ErrorMessage) + } + + if reflect.DeepEqual(ebDetails.ResponseElements, (AWSElasticBeanstalkResponseElement{})) { + // this is a current known issue with elasticbeanstalk with a suppor ticket in place + //return errors.New("response element of cloudfront distribution is missing") + } + + eb.logger.WithFields(eb.GetMetadata()).Debugf("aws event detail response elements: %#v", ebDetails.ResponseElements) + + eb.EnvironmentId = ebDetails.RequestParamaters.EnvironmentId + eb.Region = ebDetails.AWSRegion + + err = eb.RefreshState() + if err != nil { + eb.logger.Error(err.Error(), nil) + return err + } + + return nil +} + +func (eb *AWSElasticBeanstalkEnvironmentHijackableResource) NewFromPassableResource(resource PassableResource, ctx context.Context, passedInMetadata map[string]interface{}) error { + lamdbaMetadata, err := LambdaMetadataFromPassableResource(ctx, resource) + if err != nil { + return err + } + + mergedMetaData := map[string]interface{}{} + for k, v := range passedInMetadata { + mergedMetaData[k] = v + } + for k, v := range lamdbaMetadata { + mergedMetaData[k] = v + } + opts := log.LoggerOpts{ + Level: log.LogrusLevelConv(settings.GetString("log_level")), + Metadata: mergedMetaData, + } + eb.logger = instrument.GetInstrumentedLogger(opts, ctx) + structJson, err := json.Marshal(resource.Resource) + if err != nil { + eb.logger.WithFields(logrus.Fields{ + "cloud-inquisitor-resource": "aws-elasticbeanstalk-environment", + "cloud-inquisitor-error": "json marshal error", + }).WithFields(eb.GetMetadata()).Error(err.Error(), nil) + return errors.New("unable to marshal aws-route53-record-set resource") + } + + err = json.Unmarshal(structJson, eb) + if err != nil { + eb.logger.WithFields(logrus.Fields{ + "cloud-inquisitor-resource": "aws-elasticbeanstalk-environment", + "cloud-inquisitor-error": "json unmarshal error", + }).WithFields(eb.GetMetadata()).Error(err.Error(), nil) + return errors.New("unable to unmarshal aws-elasticbeanstalk-environment resource") + } + + return nil +} + +func (eb *AWSElasticBeanstalkEnvironmentHijackableResource) PublishState() error { + return nil +} diff --git a/cloud-inquisitor/resource.go b/cloud-inquisitor/resource.go index 0a93212..470e15a 100644 --- a/cloud-inquisitor/resource.go +++ b/cloud-inquisitor/resource.go @@ -31,6 +31,7 @@ const ( SERVICE_AWS_ROUTE53_RECORD Service = "AWS_ROUTE53_RECORD" SERVICE_AWS_ROUTE53_RECORD_SET Service = "AWS_ROUTE53_RECORD_SET" SERVICE_AWS_CLOUDFRONT Service = "AWS_CLOUDFRONT" + SERVICE_AWS_ELASTICBEANSTALK Service = "AWS_ELASTICBEANSTALK" ) // Resource is an interaface that vaugely describes a @@ -123,6 +124,10 @@ func (p PassableResource) GetHijackableResource(ctx context.Context, metadata ma cf := &AWSCloudFrontDistributionHijackableResource{} err := cf.NewFromPassableResource(p, ctx, metadata) return cf, err + case SERVICE_AWS_ELASTICBEANSTALK: + eb := &AWSElasticBeanstalkEnvironmentHijackableResource{} + err := eb.NewFromPassableResource(p, ctx, metadata) + return eb, err default: return nil, errors.New("no matching resource for type " + p.Type) } @@ -186,6 +191,26 @@ func NewHijackableResource(event events.CloudWatchEvent, ctx context.Context, me resource = &AWSCloudFrontDistributionHijackableResource{} resourceErr := resource.NewFromEventBus(event, ctx, metadata) return resource, resourceErr + case "aws.elasticbeanstalk": + detailMap := map[string]interface{}{} + err := json.Unmarshal(event.Detail, &detailMap) + if err != nil { + return resource, err + } + if eventName, ok := detailMap["eventName"]; ok { + switch eventName { + case "TerminateEnvironment": + resource = &AWSElasticBeanstalkEnvironmentHijackableResource{} + resourceErr := resource.NewFromEventBus(event, ctx, metadata) + return resource, resourceErr + default: + resource = &StubResource{} + return resource, errors.New("unknown route53 eventName") + } + } else { + resource = &StubResource{} + return resource, errors.New("unable to parse evetName from map") + } default: resource = &StubResource{} err := resource.NewFromEventBus(event, ctx, metadata) diff --git a/event_rules/elastic_beanstalk_dns_hijacks.json b/event_rules/elastic_beanstalk_dns_hijacks.json new file mode 100644 index 0000000..637baf2 --- /dev/null +++ b/event_rules/elastic_beanstalk_dns_hijacks.json @@ -0,0 +1,17 @@ +{ + "source": [ + "aws.elasticbeanstalk" + ], + "detail-type": [ + "AWS API Call via CloudTrail" + ], + "detail": { + "eventSource": [ + "elasticbeanstalk.amazonaws.com" + ], + "eventName": [ + "DeleteApplication", + "TerminateEnvironment" + ] + } +} \ No newline at end of file diff --git a/terraform_modules/project_role/iam.tf b/terraform_modules/project_role/iam.tf index 4ae229d..9c21e6b 100644 --- a/terraform_modules/project_role/iam.tf +++ b/terraform_modules/project_role/iam.tf @@ -93,7 +93,8 @@ data "aws_iam_policy_document" "dns_hijack_permissions" { "route53:ListHostedZonesByName", "route53:GetHostedZone", "route53:ListResourceRecordSets", - "route53:GetChange" + "route53:GetChange", + "elasticbeanstalk:DescribeEnvironments" ] resources = [ From 272c2d25eefd4f3cf4f8e1c9bdeb9ab105b9cfaa Mon Sep 17 00:00:00 2001 From: Jesse Taylor Date: Tue, 8 Sep 2020 12:59:24 -0700 Subject: [PATCH 2/9] adding in queries to find upstream dns entries --- cloud-inquisitor/aws_cloudfront_resource.go | 4 + .../aws_elasticbeanstalk_resource.go | 36 +- .../aws_route53_record_resource.go | 8 + cloud-inquisitor/aws_route53_zone_resource.go | 17 + cloud-inquisitor/graph/generated/generated.go | 1309 +++++++++++++++-- cloud-inquisitor/graph/model/models_gen.go | 70 + cloud-inquisitor/graph/schema.graphqls | 33 + cloud-inquisitor/graph/schema.resolvers.go | 51 +- cloud-inquisitor/resource.go | 97 -- cloud-inquisitor/resource_hijackable.go | 119 ++ .../serverless/hijack_graph_analyzer/main.go | 70 + .../serverless/hijack_initializer/main.go | 2 +- cloud-inquisitor/stub_resource.go | 6 +- go.mod | 4 +- go.sum | 4 + terraform_modules/event_rule/event_rule.tf | 1 + ...json => elasticbeanstalk_dns_hijacks.json} | 0 .../domain_hijack.tpl | 14 + .../step_function/step_functions.tf | 3 +- 19 files changed, 1636 insertions(+), 212 deletions(-) create mode 100644 cloud-inquisitor/graph/model/models_gen.go create mode 100644 cloud-inquisitor/resource_hijackable.go create mode 100644 cloud-inquisitor/serverless/hijack_graph_analyzer/main.go rename terraform_modules/event_rule/patterns/{elastic_beanstalk_dns_hijacks.json => elasticbeanstalk_dns_hijacks.json} (100%) diff --git a/cloud-inquisitor/aws_cloudfront_resource.go b/cloud-inquisitor/aws_cloudfront_resource.go index 150cf51..ea1bf0a 100644 --- a/cloud-inquisitor/aws_cloudfront_resource.go +++ b/cloud-inquisitor/aws_cloudfront_resource.go @@ -503,3 +503,7 @@ func (cf *AWSCloudFrontDistributionHijackableResource) PublishState() error { return nil } + +func (cf *AWSCloudFrontDistributionHijackableResource) AnalyzeForHijack() (HijackChain, error) { + return HijackChain{[]HijackChainElement{}}, nil +} diff --git a/cloud-inquisitor/aws_elasticbeanstalk_resource.go b/cloud-inquisitor/aws_elasticbeanstalk_resource.go index a5f2bc8..3a5ce38 100644 --- a/cloud-inquisitor/aws_elasticbeanstalk_resource.go +++ b/cloud-inquisitor/aws_elasticbeanstalk_resource.go @@ -18,6 +18,7 @@ import ( type AWSElasticBeanstalkEnvironmentResource struct { AccountID string + EventName string ApplicationName string EnvironmentName string EnvironmentId string @@ -75,6 +76,14 @@ func (eb *AWSElasticBeanstalkEnvironmentResource) RefreshState() error { return errors.New("settings value assume_role not set") } accountSession, err := AWSAssumeRole(eb.AccountID, settings.GetString("assume_role"), session.New()) + if err != nil { + eb.logger.WithFields(logrus.Fields{ + "cloud-inquisitor-resource": "aws-elasticbeanstalk-environment", + "cloud-inquisitor-error": "error getting regional session", + }).WithFields(eb.GetMetadata()).Error(err.Error()) + + return err + } regionalSession := accountSession.Copy(&aws.Config{ Region: aws.String(eb.Region), }) @@ -103,14 +112,26 @@ func (eb *AWSElasticBeanstalkEnvironmentResource) RefreshState() error { return err } + eb.GetLogger().Debugf("describe environments input %#v", *input) + eb.GetLogger().Debugf("describe environments result %#v", *result) + var foundEnv *elasticbeanstalk.EnvironmentDescription = nil for _, env := range result.Environments { + eb.GetLogger().Debugf("beanstalk environment: %#v", *env) if *env.EnvironmentId == eb.EnvironmentId { foundEnv = env break } } + if foundEnv == nil { + eb.logger.WithFields(logrus.Fields{ + "cloud-inquisitor-resource": "aws-elasticbeanstalk-environment", + "cloud-inquisitor-error": "DescribeEnvironments", + }).WithFields(eb.GetMetadata()).Error("could not find elasticbeanstalk environment") + return errors.New("could not find elasticbeanstalk environment") + } + eb.Status = *foundEnv.Status eb.ApplicationName = *foundEnv.ApplicationName eb.CNAME = *foundEnv.CNAME @@ -150,6 +171,10 @@ type AWSElasticBeanstalkEnvironmentHijackableResource struct { } func (eb *AWSElasticBeanstalkEnvironmentHijackableResource) NewFromEventBus(event events.CloudWatchEvent, ctx context.Context, passedInMetadata map[string]interface{}) error { + // set account id first + eb.AccountID = event.AccountID + eb.Region = event.Region + defaultMetadata, err := DefaultLambdaMetadata(ctx) if err != nil { return err @@ -186,10 +211,11 @@ func (eb *AWSElasticBeanstalkEnvironmentHijackableResource) NewFromEventBus(even //return errors.New("response element of cloudfront distribution is missing") } + // set detail specific fields eb.logger.WithFields(eb.GetMetadata()).Debugf("aws event detail response elements: %#v", ebDetails.ResponseElements) - + eb.logger.WithFields(eb.GetMetadata()).Debugf("aws event detail request parameters: %#v", ebDetails.RequestParamaters) eb.EnvironmentId = ebDetails.RequestParamaters.EnvironmentId - eb.Region = ebDetails.AWSRegion + eb.EventName = ebDetails.EventName err = eb.RefreshState() if err != nil { @@ -224,7 +250,7 @@ func (eb *AWSElasticBeanstalkEnvironmentHijackableResource) NewFromPassableResou "cloud-inquisitor-resource": "aws-elasticbeanstalk-environment", "cloud-inquisitor-error": "json marshal error", }).WithFields(eb.GetMetadata()).Error(err.Error(), nil) - return errors.New("unable to marshal aws-route53-record-set resource") + return errors.New("unable to marshal aws-elasticbeanstalk-environment resource") } err = json.Unmarshal(structJson, eb) @@ -242,3 +268,7 @@ func (eb *AWSElasticBeanstalkEnvironmentHijackableResource) NewFromPassableResou func (eb *AWSElasticBeanstalkEnvironmentHijackableResource) PublishState() error { return nil } + +func (eb *AWSElasticBeanstalkEnvironmentHijackableResource) AnalyzeForHijack() (HijackChain, error) { + return HijackChain{[]HijackChainElement{}}, nil +} diff --git a/cloud-inquisitor/aws_route53_record_resource.go b/cloud-inquisitor/aws_route53_record_resource.go index ee9ba54..d7e108d 100644 --- a/cloud-inquisitor/aws_route53_record_resource.go +++ b/cloud-inquisitor/aws_route53_record_resource.go @@ -285,6 +285,10 @@ func (r *AWSRoute53RecordSet) isPending() (bool, error) { return true, nil } +func (r *AWSRoute53RecordSet) AnalyzeForHijack() (HijackChain, error) { + return HijackChain{[]HijackChainElement{}}, nil +} + func (r *AWSRoute53Record) NewFromEventBus(_ events.CloudWatchEvent, _ context.Context, _ map[string]interface{}) error { // this is a stub since the event for record creation is wrapped in Route53 Record Set event return nil @@ -532,3 +536,7 @@ func (r *AWSRoute53Record) createRecordEntries() error { r.logger.WithFields(r.GetMetadata()).Debug("adding account/zone to graph") return nil } + +func (r *AWSRoute53Record) AnalyzeForHijack() (HijackChain, error) { + return HijackChain{[]HijackChainElement{}}, nil +} diff --git a/cloud-inquisitor/aws_route53_zone_resource.go b/cloud-inquisitor/aws_route53_zone_resource.go index 31ddbe5..ae7b49a 100644 --- a/cloud-inquisitor/aws_route53_zone_resource.go +++ b/cloud-inquisitor/aws_route53_zone_resource.go @@ -220,6 +220,19 @@ func (r *AWSRoute53Zone) RefreshState() error { } func (r *AWSRoute53Zone) PublishState() error { + switch r.EventName { + case "CreateHostedZone": + return r.createZoneEntries() + case "DeleteHostedZone": + // implement removal + return nil + default: + return nil + } + return nil +} + +func (r *AWSRoute53Zone) createZoneEntries() error { db, err := graph.NewDBConnection() defer db.Close() if err != nil { @@ -323,3 +336,7 @@ func (r *AWSRoute53Zone) isPending() (bool, error) { return true, nil } + +func (r *AWSRoute53Zone) AnalyzeForHijack() (HijackChain, error) { + return HijackChain{[]HijackChainElement{}}, nil +} diff --git a/cloud-inquisitor/graph/generated/generated.go b/cloud-inquisitor/graph/generated/generated.go index 8946932..2ff9a73 100644 --- a/cloud-inquisitor/graph/generated/generated.go +++ b/cloud-inquisitor/graph/generated/generated.go @@ -61,6 +61,20 @@ type ComplexityRoot struct { Origins func(childComplexity int) int } + HijackableResource struct { + Account func(childComplexity int) int + ID func(childComplexity int) int + Type func(childComplexity int) int + Value func(childComplexity int) int + } + + HijackableResourceChain struct { + Downsteam func(childComplexity int) int + ID func(childComplexity int) int + Resource func(childComplexity int) int + Upstream func(childComplexity int) int + } + Origin struct { Domain func(childComplexity int) int OriginID func(childComplexity int) int @@ -72,18 +86,25 @@ type ComplexityRoot struct { } Query struct { - Account func(childComplexity int, id string) int - Accounts func(childComplexity int) int - Distribution func(childComplexity int, id string) int - Distributions func(childComplexity int) int - Origin func(childComplexity int, id string) int - Origins func(childComplexity int) int - Record func(childComplexity int, id string) int - Records func(childComplexity int) int - Value func(childComplexity int, id string) int - Values func(childComplexity int) int - Zone func(childComplexity int, id string) int - Zones func(childComplexity int) int + Account func(childComplexity int, id string) int + Accounts func(childComplexity int) int + Distribution func(childComplexity int, id string) int + Distributions func(childComplexity int) int + GetElasticbeanstalkUpstreamHijack func(childComplexity int, endpoints []string) int + HijackChainByDomain func(childComplexity int, domain string) int + Origin func(childComplexity int, id string) int + OriginGroups func(childComplexity int) int + Origins func(childComplexity int) int + PointedAtByDistribution func(childComplexity int, domain string) int + PointedAtByOrigin func(childComplexity int, domain string) int + PointedAtByOriginGroup func(childComplexity int, domain string) int + PointedAtByRecords func(childComplexity int, domain string) int + Record func(childComplexity int, id string) int + Records func(childComplexity int) int + Value func(childComplexity int, id string) int + Values func(childComplexity int) int + Zone func(childComplexity int, id string) int + Zones func(childComplexity int) int } Record struct { @@ -123,12 +144,19 @@ type QueryResolver interface { Zone(ctx context.Context, id string) (*model.Zone, error) Records(ctx context.Context) ([]*model.Record, error) Record(ctx context.Context, id string) (*model.Record, error) + PointedAtByRecords(ctx context.Context, domain string) ([]*model.Record, error) Values(ctx context.Context) ([]*model.Value, error) Value(ctx context.Context, id string) (*model.Value, error) Distributions(ctx context.Context) ([]*model.Distribution, error) Distribution(ctx context.Context, id string) (*model.Distribution, error) + PointedAtByDistribution(ctx context.Context, domain string) ([]*model.Distribution, error) Origins(ctx context.Context) ([]*model.Origin, error) Origin(ctx context.Context, id string) (*model.Origin, error) + PointedAtByOrigin(ctx context.Context, domain string) ([]*model.Origin, error) + OriginGroups(ctx context.Context) ([]*model.OriginGroup, error) + PointedAtByOriginGroup(ctx context.Context, domain string) ([]*model.OriginGroup, error) + HijackChainByDomain(ctx context.Context, domain string) (*model.HijackableResourceChain, error) + GetElasticbeanstalkUpstreamHijack(ctx context.Context, endpoints []string) ([]*model.HijackableResource, error) } type RecordResolver interface { Values(ctx context.Context, obj *model.Record) ([]*model.Value, error) @@ -221,6 +249,62 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Distribution.Origins(childComplexity), true + case "HijackableResource.account": + if e.complexity.HijackableResource.Account == nil { + break + } + + return e.complexity.HijackableResource.Account(childComplexity), true + + case "HijackableResource.id": + if e.complexity.HijackableResource.ID == nil { + break + } + + return e.complexity.HijackableResource.ID(childComplexity), true + + case "HijackableResource.type": + if e.complexity.HijackableResource.Type == nil { + break + } + + return e.complexity.HijackableResource.Type(childComplexity), true + + case "HijackableResource.value": + if e.complexity.HijackableResource.Value == nil { + break + } + + return e.complexity.HijackableResource.Value(childComplexity), true + + case "HijackableResourceChain.downsteam": + if e.complexity.HijackableResourceChain.Downsteam == nil { + break + } + + return e.complexity.HijackableResourceChain.Downsteam(childComplexity), true + + case "HijackableResourceChain.id": + if e.complexity.HijackableResourceChain.ID == nil { + break + } + + return e.complexity.HijackableResourceChain.ID(childComplexity), true + + case "HijackableResourceChain.resource": + if e.complexity.HijackableResourceChain.Resource == nil { + break + } + + return e.complexity.HijackableResourceChain.Resource(childComplexity), true + + case "HijackableResourceChain.upstream": + if e.complexity.HijackableResourceChain.Upstream == nil { + break + } + + return e.complexity.HijackableResourceChain.Upstream(childComplexity), true + case "Origin.domain": if e.complexity.Origin.Domain == nil { break @@ -287,6 +371,30 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.Distributions(childComplexity), true + case "Query.getElasticbeanstalkUpstreamHijack": + if e.complexity.Query.GetElasticbeanstalkUpstreamHijack == nil { + break + } + + args, err := ec.field_Query_getElasticbeanstalkUpstreamHijack_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Query.GetElasticbeanstalkUpstreamHijack(childComplexity, args["endpoints"].([]string)), true + + case "Query.hijackChainByDomain": + if e.complexity.Query.HijackChainByDomain == nil { + break + } + + args, err := ec.field_Query_hijackChainByDomain_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Query.HijackChainByDomain(childComplexity, args["domain"].(string)), true + case "Query.origin": if e.complexity.Query.Origin == nil { break @@ -299,6 +407,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.Origin(childComplexity, args["id"].(string)), true + case "Query.originGroups": + if e.complexity.Query.OriginGroups == nil { + break + } + + return e.complexity.Query.OriginGroups(childComplexity), true + case "Query.origins": if e.complexity.Query.Origins == nil { break @@ -306,6 +421,54 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.Origins(childComplexity), true + case "Query.pointedAtByDistribution": + if e.complexity.Query.PointedAtByDistribution == nil { + break + } + + args, err := ec.field_Query_pointedAtByDistribution_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Query.PointedAtByDistribution(childComplexity, args["domain"].(string)), true + + case "Query.pointedAtByOrigin": + if e.complexity.Query.PointedAtByOrigin == nil { + break + } + + args, err := ec.field_Query_pointedAtByOrigin_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Query.PointedAtByOrigin(childComplexity, args["domain"].(string)), true + + case "Query.pointedAtByOriginGroup": + if e.complexity.Query.PointedAtByOriginGroup == nil { + break + } + + args, err := ec.field_Query_pointedAtByOriginGroup_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Query.PointedAtByOriginGroup(childComplexity, args["domain"].(string)), true + + case "Query.pointedAtByRecords": + if e.complexity.Query.PointedAtByRecords == nil { + break + } + + args, err := ec.field_Query_pointedAtByRecords_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Query.PointedAtByRecords(childComplexity, args["domain"].(string)), true + case "Query.record": if e.complexity.Query.Record == nil { break @@ -488,7 +651,15 @@ func (ec *executionContext) introspectType(name string) (*introspection.Type, er } var sources = []*ast.Source{ - &ast.Source{Name: "schema.graphqls", Input: `type Account { + &ast.Source{Name: "schema.graphqls", Input: `enum Type { + ACCOUNT + ZONE + RECORD + DISTRIBUTION + ORIGIN +} + +type Account { accountID: ID! zones: [Zone!]! zone(id: ID!): Zone! @@ -532,6 +703,20 @@ type OriginGroup { origins: [Value!]! } +type HijackableResource { + id: ID! + account: String! + type: Type! + value: Value! +} + +type HijackableResourceChain { + id: ID! + resource: HijackableResource! + upstream: [HijackableResource!]! + downsteam: [HijackableResource!]! +} + type Query { accounts: [Account!]! account(id: ID!): Account! @@ -541,15 +726,26 @@ type Query { records: [Record!]! record(id: ID!): Record! + pointedAtByRecords(domain: String!): [Record!]! values: [Value!]! value(id: ID!): Value! distributions: [Distribution!]! distribution(id: ID!): Distribution! + pointedAtByDistribution(domain: String!): [Distribution!]! origins: [Origin!]! origin(id: ID!): Origin + pointedAtByOrigin(domain: String!): [Origin!]! + + originGroups: [OriginGroup!]! + pointedAtByOriginGroup(domain: String!): [OriginGroup!]! + + hijackChainByDomain(domain: String!): HijackableResourceChain! + + + getElasticbeanstalkUpstreamHijack(endpoints: [String!]!): [HijackableResource!]! }`, BuiltIn: false}, } var parsedSchema = gqlparser.MustLoadSchema(sources...) @@ -614,6 +810,34 @@ func (ec *executionContext) field_Query_distribution_args(ctx context.Context, r return args, nil } +func (ec *executionContext) field_Query_getElasticbeanstalkUpstreamHijack_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 []string + if tmp, ok := rawArgs["endpoints"]; ok { + arg0, err = ec.unmarshalNString2ᚕstringᚄ(ctx, tmp) + if err != nil { + return nil, err + } + } + args["endpoints"] = arg0 + return args, nil +} + +func (ec *executionContext) field_Query_hijackChainByDomain_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 string + if tmp, ok := rawArgs["domain"]; ok { + arg0, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["domain"] = arg0 + return args, nil +} + func (ec *executionContext) field_Query_origin_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -628,6 +852,62 @@ func (ec *executionContext) field_Query_origin_args(ctx context.Context, rawArgs return args, nil } +func (ec *executionContext) field_Query_pointedAtByDistribution_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 string + if tmp, ok := rawArgs["domain"]; ok { + arg0, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["domain"] = arg0 + return args, nil +} + +func (ec *executionContext) field_Query_pointedAtByOriginGroup_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 string + if tmp, ok := rawArgs["domain"]; ok { + arg0, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["domain"] = arg0 + return args, nil +} + +func (ec *executionContext) field_Query_pointedAtByOrigin_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 string + if tmp, ok := rawArgs["domain"]; ok { + arg0, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["domain"] = arg0 + return args, nil +} + +func (ec *executionContext) field_Query_pointedAtByRecords_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 string + if tmp, ok := rawArgs["domain"]; ok { + arg0, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["domain"] = arg0 + return args, nil +} + func (ec *executionContext) field_Query_record_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -1033,7 +1313,7 @@ func (ec *executionContext) _Distribution_originGroups(ctx context.Context, fiel return ec.marshalNOriginGroup2ᚕᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐOriginGroupᚄ(ctx, field.Selections, res) } -func (ec *executionContext) _Origin_originID(ctx context.Context, field graphql.CollectedField, obj *model.Origin) (ret graphql.Marshaler) { +func (ec *executionContext) _HijackableResource_id(ctx context.Context, field graphql.CollectedField, obj *model.HijackableResource) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -1041,7 +1321,7 @@ func (ec *executionContext) _Origin_originID(ctx context.Context, field graphql. } }() fc := &graphql.FieldContext{ - Object: "Origin", + Object: "HijackableResource", Field: field, Args: nil, IsMethod: false, @@ -1050,7 +1330,7 @@ func (ec *executionContext) _Origin_originID(ctx context.Context, field graphql. ctx = graphql.WithFieldContext(ctx, fc) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.OriginID, nil + return obj.ID, nil }) if err != nil { ec.Error(ctx, err) @@ -1067,7 +1347,7 @@ func (ec *executionContext) _Origin_originID(ctx context.Context, field graphql. return ec.marshalNID2string(ctx, field.Selections, res) } -func (ec *executionContext) _Origin_domain(ctx context.Context, field graphql.CollectedField, obj *model.Origin) (ret graphql.Marshaler) { +func (ec *executionContext) _HijackableResource_account(ctx context.Context, field graphql.CollectedField, obj *model.HijackableResource) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -1075,7 +1355,7 @@ func (ec *executionContext) _Origin_domain(ctx context.Context, field graphql.Co } }() fc := &graphql.FieldContext{ - Object: "Origin", + Object: "HijackableResource", Field: field, Args: nil, IsMethod: false, @@ -1084,7 +1364,7 @@ func (ec *executionContext) _Origin_domain(ctx context.Context, field graphql.Co ctx = graphql.WithFieldContext(ctx, fc) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Domain, nil + return obj.Account, nil }) if err != nil { ec.Error(ctx, err) @@ -1101,7 +1381,7 @@ func (ec *executionContext) _Origin_domain(ctx context.Context, field graphql.Co return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _OriginGroup_groupID(ctx context.Context, field graphql.CollectedField, obj *model.OriginGroup) (ret graphql.Marshaler) { +func (ec *executionContext) _HijackableResource_type(ctx context.Context, field graphql.CollectedField, obj *model.HijackableResource) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -1109,7 +1389,7 @@ func (ec *executionContext) _OriginGroup_groupID(ctx context.Context, field grap } }() fc := &graphql.FieldContext{ - Object: "OriginGroup", + Object: "HijackableResource", Field: field, Args: nil, IsMethod: false, @@ -1118,7 +1398,7 @@ func (ec *executionContext) _OriginGroup_groupID(ctx context.Context, field grap ctx = graphql.WithFieldContext(ctx, fc) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.GroupID, nil + return obj.Type, nil }) if err != nil { ec.Error(ctx, err) @@ -1130,12 +1410,12 @@ func (ec *executionContext) _OriginGroup_groupID(ctx context.Context, field grap } return graphql.Null } - res := resTmp.(string) + res := resTmp.(model.Type) fc.Result = res - return ec.marshalNID2string(ctx, field.Selections, res) + return ec.marshalNType2githubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐType(ctx, field.Selections, res) } -func (ec *executionContext) _OriginGroup_origins(ctx context.Context, field graphql.CollectedField, obj *model.OriginGroup) (ret graphql.Marshaler) { +func (ec *executionContext) _HijackableResource_value(ctx context.Context, field graphql.CollectedField, obj *model.HijackableResource) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -1143,7 +1423,7 @@ func (ec *executionContext) _OriginGroup_origins(ctx context.Context, field grap } }() fc := &graphql.FieldContext{ - Object: "OriginGroup", + Object: "HijackableResource", Field: field, Args: nil, IsMethod: false, @@ -1152,7 +1432,7 @@ func (ec *executionContext) _OriginGroup_origins(ctx context.Context, field grap ctx = graphql.WithFieldContext(ctx, fc) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Origins, nil + return obj.Value, nil }) if err != nil { ec.Error(ctx, err) @@ -1164,12 +1444,12 @@ func (ec *executionContext) _OriginGroup_origins(ctx context.Context, field grap } return graphql.Null } - res := resTmp.([]model.Value) + res := resTmp.(*model.Value) fc.Result = res - return ec.marshalNValue2ᚕgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐValueᚄ(ctx, field.Selections, res) + return ec.marshalNValue2ᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐValue(ctx, field.Selections, res) } -func (ec *executionContext) _Query_accounts(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { +func (ec *executionContext) _HijackableResourceChain_id(ctx context.Context, field graphql.CollectedField, obj *model.HijackableResourceChain) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -1177,16 +1457,16 @@ func (ec *executionContext) _Query_accounts(ctx context.Context, field graphql.C } }() fc := &graphql.FieldContext{ - Object: "Query", + Object: "HijackableResourceChain", Field: field, Args: nil, - IsMethod: true, + IsMethod: false, } ctx = graphql.WithFieldContext(ctx, fc) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().Accounts(rctx) + return obj.ID, nil }) if err != nil { ec.Error(ctx, err) @@ -1198,12 +1478,12 @@ func (ec *executionContext) _Query_accounts(ctx context.Context, field graphql.C } return graphql.Null } - res := resTmp.([]*model.Account) + res := resTmp.(string) fc.Result = res - return ec.marshalNAccount2ᚕᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐAccountᚄ(ctx, field.Selections, res) + return ec.marshalNID2string(ctx, field.Selections, res) } -func (ec *executionContext) _Query_account(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { +func (ec *executionContext) _HijackableResourceChain_resource(ctx context.Context, field graphql.CollectedField, obj *model.HijackableResourceChain) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -1211,23 +1491,16 @@ func (ec *executionContext) _Query_account(ctx context.Context, field graphql.Co } }() fc := &graphql.FieldContext{ - Object: "Query", + Object: "HijackableResourceChain", Field: field, Args: nil, - IsMethod: true, + IsMethod: false, } ctx = graphql.WithFieldContext(ctx, fc) - rawArgs := field.ArgumentMap(ec.Variables) - args, err := ec.field_Query_account_args(ctx, rawArgs) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - fc.Args = args resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().Account(rctx, args["id"].(string)) + return obj.Resource, nil }) if err != nil { ec.Error(ctx, err) @@ -1239,12 +1512,12 @@ func (ec *executionContext) _Query_account(ctx context.Context, field graphql.Co } return graphql.Null } - res := resTmp.(*model.Account) + res := resTmp.(*model.HijackableResource) fc.Result = res - return ec.marshalNAccount2ᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐAccount(ctx, field.Selections, res) + return ec.marshalNHijackableResource2ᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐHijackableResource(ctx, field.Selections, res) } -func (ec *executionContext) _Query_zones(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { +func (ec *executionContext) _HijackableResourceChain_upstream(ctx context.Context, field graphql.CollectedField, obj *model.HijackableResourceChain) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -1252,16 +1525,16 @@ func (ec *executionContext) _Query_zones(ctx context.Context, field graphql.Coll } }() fc := &graphql.FieldContext{ - Object: "Query", + Object: "HijackableResourceChain", Field: field, Args: nil, - IsMethod: true, + IsMethod: false, } ctx = graphql.WithFieldContext(ctx, fc) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().Zones(rctx) + return obj.Upstream, nil }) if err != nil { ec.Error(ctx, err) @@ -1273,12 +1546,12 @@ func (ec *executionContext) _Query_zones(ctx context.Context, field graphql.Coll } return graphql.Null } - res := resTmp.([]*model.Zone) + res := resTmp.([]*model.HijackableResource) fc.Result = res - return ec.marshalNZone2ᚕᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐZoneᚄ(ctx, field.Selections, res) + return ec.marshalNHijackableResource2ᚕᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐHijackableResourceᚄ(ctx, field.Selections, res) } -func (ec *executionContext) _Query_zone(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { +func (ec *executionContext) _HijackableResourceChain_downsteam(ctx context.Context, field graphql.CollectedField, obj *model.HijackableResourceChain) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -1286,23 +1559,16 @@ func (ec *executionContext) _Query_zone(ctx context.Context, field graphql.Colle } }() fc := &graphql.FieldContext{ - Object: "Query", + Object: "HijackableResourceChain", Field: field, Args: nil, - IsMethod: true, + IsMethod: false, } ctx = graphql.WithFieldContext(ctx, fc) - rawArgs := field.ArgumentMap(ec.Variables) - args, err := ec.field_Query_zone_args(ctx, rawArgs) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - fc.Args = args resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().Zone(rctx, args["id"].(string)) + return obj.Downsteam, nil }) if err != nil { ec.Error(ctx, err) @@ -1314,12 +1580,12 @@ func (ec *executionContext) _Query_zone(ctx context.Context, field graphql.Colle } return graphql.Null } - res := resTmp.(*model.Zone) + res := resTmp.([]*model.HijackableResource) fc.Result = res - return ec.marshalNZone2ᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐZone(ctx, field.Selections, res) + return ec.marshalNHijackableResource2ᚕᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐHijackableResourceᚄ(ctx, field.Selections, res) } -func (ec *executionContext) _Query_records(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { +func (ec *executionContext) _Origin_originID(ctx context.Context, field graphql.CollectedField, obj *model.Origin) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -1327,16 +1593,16 @@ func (ec *executionContext) _Query_records(ctx context.Context, field graphql.Co } }() fc := &graphql.FieldContext{ - Object: "Query", + Object: "Origin", Field: field, Args: nil, - IsMethod: true, + IsMethod: false, } ctx = graphql.WithFieldContext(ctx, fc) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().Records(rctx) + return obj.OriginID, nil }) if err != nil { ec.Error(ctx, err) @@ -1348,12 +1614,12 @@ func (ec *executionContext) _Query_records(ctx context.Context, field graphql.Co } return graphql.Null } - res := resTmp.([]*model.Record) + res := resTmp.(string) fc.Result = res - return ec.marshalNRecord2ᚕᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐRecordᚄ(ctx, field.Selections, res) + return ec.marshalNID2string(ctx, field.Selections, res) } -func (ec *executionContext) _Query_record(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { +func (ec *executionContext) _Origin_domain(ctx context.Context, field graphql.CollectedField, obj *model.Origin) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -1361,23 +1627,350 @@ func (ec *executionContext) _Query_record(ctx context.Context, field graphql.Col } }() fc := &graphql.FieldContext{ - Object: "Query", + Object: "Origin", Field: field, Args: nil, - IsMethod: true, + IsMethod: false, } ctx = graphql.WithFieldContext(ctx, fc) - rawArgs := field.ArgumentMap(ec.Variables) - args, err := ec.field_Query_record_args(ctx, rawArgs) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Domain, nil + }) if err != nil { ec.Error(ctx, err) return graphql.Null } - fc.Args = args - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _OriginGroup_groupID(ctx context.Context, field graphql.CollectedField, obj *model.OriginGroup) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "OriginGroup", + Field: field, + Args: nil, + IsMethod: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.GroupID, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNID2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _OriginGroup_origins(ctx context.Context, field graphql.CollectedField, obj *model.OriginGroup) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "OriginGroup", + Field: field, + Args: nil, + IsMethod: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Origins, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]model.Value) + fc.Result = res + return ec.marshalNValue2ᚕgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐValueᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) _Query_accounts(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().Accounts(rctx) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]*model.Account) + fc.Result = res + return ec.marshalNAccount2ᚕᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐAccountᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) _Query_account(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Query_account_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().Account(rctx, args["id"].(string)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*model.Account) + fc.Result = res + return ec.marshalNAccount2ᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐAccount(ctx, field.Selections, res) +} + +func (ec *executionContext) _Query_zones(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().Zones(rctx) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]*model.Zone) + fc.Result = res + return ec.marshalNZone2ᚕᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐZoneᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) _Query_zone(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Query_zone_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().Zone(rctx, args["id"].(string)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*model.Zone) + fc.Result = res + return ec.marshalNZone2ᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐZone(ctx, field.Selections, res) +} + +func (ec *executionContext) _Query_records(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().Records(rctx) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]*model.Record) + fc.Result = res + return ec.marshalNRecord2ᚕᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐRecordᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) _Query_record(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Query_record_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().Record(rctx, args["id"].(string)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*model.Record) + fc.Result = res + return ec.marshalNRecord2ᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐRecord(ctx, field.Selections, res) +} + +func (ec *executionContext) _Query_pointedAtByRecords(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Query_pointedAtByRecords_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().Record(rctx, args["id"].(string)) + return ec.resolvers.Query().PointedAtByRecords(rctx, args["domain"].(string)) }) if err != nil { ec.Error(ctx, err) @@ -1389,9 +1982,9 @@ func (ec *executionContext) _Query_record(ctx context.Context, field graphql.Col } return graphql.Null } - res := resTmp.(*model.Record) + res := resTmp.([]*model.Record) fc.Result = res - return ec.marshalNRecord2ᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐRecord(ctx, field.Selections, res) + return ec.marshalNRecord2ᚕᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐRecordᚄ(ctx, field.Selections, res) } func (ec *executionContext) _Query_values(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { @@ -1519,7 +2112,236 @@ func (ec *executionContext) _Query_distribution(ctx context.Context, field graph ctx = graphql.WithFieldContext(ctx, fc) rawArgs := field.ArgumentMap(ec.Variables) - args, err := ec.field_Query_distribution_args(ctx, rawArgs) + args, err := ec.field_Query_distribution_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().Distribution(rctx, args["id"].(string)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*model.Distribution) + fc.Result = res + return ec.marshalNDistribution2ᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐDistribution(ctx, field.Selections, res) +} + +func (ec *executionContext) _Query_pointedAtByDistribution(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Query_pointedAtByDistribution_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().PointedAtByDistribution(rctx, args["domain"].(string)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]*model.Distribution) + fc.Result = res + return ec.marshalNDistribution2ᚕᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐDistributionᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) _Query_origins(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().Origins(rctx) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]*model.Origin) + fc.Result = res + return ec.marshalNOrigin2ᚕᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐOriginᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) _Query_origin(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Query_origin_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().Origin(rctx, args["id"].(string)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*model.Origin) + fc.Result = res + return ec.marshalOOrigin2ᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐOrigin(ctx, field.Selections, res) +} + +func (ec *executionContext) _Query_pointedAtByOrigin(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Query_pointedAtByOrigin_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().PointedAtByOrigin(rctx, args["domain"].(string)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]*model.Origin) + fc.Result = res + return ec.marshalNOrigin2ᚕᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐOriginᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) _Query_originGroups(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().OriginGroups(rctx) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]*model.OriginGroup) + fc.Result = res + return ec.marshalNOriginGroup2ᚕᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐOriginGroupᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) _Query_pointedAtByOriginGroup(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Query_pointedAtByOriginGroup_args(ctx, rawArgs) if err != nil { ec.Error(ctx, err) return graphql.Null @@ -1527,7 +2349,7 @@ func (ec *executionContext) _Query_distribution(ctx context.Context, field graph fc.Args = args resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().Distribution(rctx, args["id"].(string)) + return ec.resolvers.Query().PointedAtByOriginGroup(rctx, args["domain"].(string)) }) if err != nil { ec.Error(ctx, err) @@ -1539,12 +2361,12 @@ func (ec *executionContext) _Query_distribution(ctx context.Context, field graph } return graphql.Null } - res := resTmp.(*model.Distribution) + res := resTmp.([]*model.OriginGroup) fc.Result = res - return ec.marshalNDistribution2ᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐDistribution(ctx, field.Selections, res) + return ec.marshalNOriginGroup2ᚕᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐOriginGroupᚄ(ctx, field.Selections, res) } -func (ec *executionContext) _Query_origins(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { +func (ec *executionContext) _Query_hijackChainByDomain(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -1559,9 +2381,16 @@ func (ec *executionContext) _Query_origins(ctx context.Context, field graphql.Co } ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Query_hijackChainByDomain_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().Origins(rctx) + return ec.resolvers.Query().HijackChainByDomain(rctx, args["domain"].(string)) }) if err != nil { ec.Error(ctx, err) @@ -1573,12 +2402,12 @@ func (ec *executionContext) _Query_origins(ctx context.Context, field graphql.Co } return graphql.Null } - res := resTmp.([]*model.Origin) + res := resTmp.(*model.HijackableResourceChain) fc.Result = res - return ec.marshalNOrigin2ᚕᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐOriginᚄ(ctx, field.Selections, res) + return ec.marshalNHijackableResourceChain2ᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐHijackableResourceChain(ctx, field.Selections, res) } -func (ec *executionContext) _Query_origin(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { +func (ec *executionContext) _Query_getElasticbeanstalkUpstreamHijack(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -1594,7 +2423,7 @@ func (ec *executionContext) _Query_origin(ctx context.Context, field graphql.Col ctx = graphql.WithFieldContext(ctx, fc) rawArgs := field.ArgumentMap(ec.Variables) - args, err := ec.field_Query_origin_args(ctx, rawArgs) + args, err := ec.field_Query_getElasticbeanstalkUpstreamHijack_args(ctx, rawArgs) if err != nil { ec.Error(ctx, err) return graphql.Null @@ -1602,18 +2431,21 @@ func (ec *executionContext) _Query_origin(ctx context.Context, field graphql.Col fc.Args = args resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().Origin(rctx, args["id"].(string)) + return ec.resolvers.Query().GetElasticbeanstalkUpstreamHijack(rctx, args["endpoints"].([]string)) }) if err != nil { ec.Error(ctx, err) return graphql.Null } if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } return graphql.Null } - res := resTmp.(*model.Origin) + res := resTmp.([]*model.HijackableResource) fc.Result = res - return ec.marshalOOrigin2ᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐOrigin(ctx, field.Selections, res) + return ec.marshalNHijackableResource2ᚕᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐHijackableResourceᚄ(ctx, field.Selections, res) } func (ec *executionContext) _Query___type(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { @@ -3238,6 +4070,90 @@ func (ec *executionContext) _Distribution(ctx context.Context, sel ast.Selection return out } +var hijackableResourceImplementors = []string{"HijackableResource"} + +func (ec *executionContext) _HijackableResource(ctx context.Context, sel ast.SelectionSet, obj *model.HijackableResource) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, hijackableResourceImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("HijackableResource") + case "id": + out.Values[i] = ec._HijackableResource_id(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "account": + out.Values[i] = ec._HijackableResource_account(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "type": + out.Values[i] = ec._HijackableResource_type(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "value": + out.Values[i] = ec._HijackableResource_value(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + +var hijackableResourceChainImplementors = []string{"HijackableResourceChain"} + +func (ec *executionContext) _HijackableResourceChain(ctx context.Context, sel ast.SelectionSet, obj *model.HijackableResourceChain) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, hijackableResourceChainImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("HijackableResourceChain") + case "id": + out.Values[i] = ec._HijackableResourceChain_id(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "resource": + out.Values[i] = ec._HijackableResourceChain_resource(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "upstream": + out.Values[i] = ec._HijackableResourceChain_upstream(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "downsteam": + out.Values[i] = ec._HijackableResourceChain_downsteam(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + var originImplementors = []string{"Origin"} func (ec *executionContext) _Origin(ctx context.Context, sel ast.SelectionSet, obj *model.Origin) graphql.Marshaler { @@ -3401,6 +4317,20 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr } return res }) + case "pointedAtByRecords": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_pointedAtByRecords(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "values": field := field out.Concurrently(i, func() (res graphql.Marshaler) { @@ -3457,6 +4387,20 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr } return res }) + case "pointedAtByDistribution": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_pointedAtByDistribution(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "origins": field := field out.Concurrently(i, func() (res graphql.Marshaler) { @@ -3482,6 +4426,76 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr res = ec._Query_origin(ctx, field) return res }) + case "pointedAtByOrigin": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_pointedAtByOrigin(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) + case "originGroups": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_originGroups(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) + case "pointedAtByOriginGroup": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_pointedAtByOriginGroup(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) + case "hijackChainByDomain": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_hijackChainByDomain(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) + case "getElasticbeanstalkUpstreamHijack": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_getElasticbeanstalkUpstreamHijack(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "__type": out.Values[i] = ec._Query___type(ctx, field) case "__schema": @@ -4001,6 +5015,71 @@ func (ec *executionContext) marshalNDistribution2ᚖgithubᚗcomᚋRiotGamesᚋc return ec._Distribution(ctx, sel, v) } +func (ec *executionContext) marshalNHijackableResource2githubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐHijackableResource(ctx context.Context, sel ast.SelectionSet, v model.HijackableResource) graphql.Marshaler { + return ec._HijackableResource(ctx, sel, &v) +} + +func (ec *executionContext) marshalNHijackableResource2ᚕᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐHijackableResourceᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.HijackableResource) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNHijackableResource2ᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐHijackableResource(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + return ret +} + +func (ec *executionContext) marshalNHijackableResource2ᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐHijackableResource(ctx context.Context, sel ast.SelectionSet, v *model.HijackableResource) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + return ec._HijackableResource(ctx, sel, v) +} + +func (ec *executionContext) marshalNHijackableResourceChain2githubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐHijackableResourceChain(ctx context.Context, sel ast.SelectionSet, v model.HijackableResourceChain) graphql.Marshaler { + return ec._HijackableResourceChain(ctx, sel, &v) +} + +func (ec *executionContext) marshalNHijackableResourceChain2ᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐHijackableResourceChain(ctx context.Context, sel ast.SelectionSet, v *model.HijackableResourceChain) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + return ec._HijackableResourceChain(ctx, sel, v) +} + func (ec *executionContext) unmarshalNID2string(ctx context.Context, v interface{}) (string, error) { return graphql.UnmarshalID(v) } @@ -4182,6 +5261,44 @@ func (ec *executionContext) marshalNString2string(ctx context.Context, sel ast.S return res } +func (ec *executionContext) unmarshalNString2ᚕstringᚄ(ctx context.Context, v interface{}) ([]string, error) { + var vSlice []interface{} + if v != nil { + if tmp1, ok := v.([]interface{}); ok { + vSlice = tmp1 + } else { + vSlice = []interface{}{v} + } + } + var err error + res := make([]string, len(vSlice)) + for i := range vSlice { + res[i], err = ec.unmarshalNString2string(ctx, vSlice[i]) + if err != nil { + return nil, err + } + } + return res, nil +} + +func (ec *executionContext) marshalNString2ᚕstringᚄ(ctx context.Context, sel ast.SelectionSet, v []string) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + for i := range v { + ret[i] = ec.marshalNString2string(ctx, sel, v[i]) + } + + return ret +} + +func (ec *executionContext) unmarshalNType2githubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐType(ctx context.Context, v interface{}) (model.Type, error) { + var res model.Type + return res, res.UnmarshalGQL(v) +} + +func (ec *executionContext) marshalNType2githubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐType(ctx context.Context, sel ast.SelectionSet, v model.Type) graphql.Marshaler { + return v +} + func (ec *executionContext) marshalNValue2githubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐValue(ctx context.Context, sel ast.SelectionSet, v model.Value) graphql.Marshaler { return ec._Value(ctx, sel, &v) } diff --git a/cloud-inquisitor/graph/model/models_gen.go b/cloud-inquisitor/graph/model/models_gen.go new file mode 100644 index 0000000..7a9f81e --- /dev/null +++ b/cloud-inquisitor/graph/model/models_gen.go @@ -0,0 +1,70 @@ +// Code generated by github.com/99designs/gqlgen, DO NOT EDIT. + +package model + +import ( + "fmt" + "io" + "strconv" +) + +type HijackableResource struct { + ID string `json:"id"` + Account string `json:"account"` + Type Type `json:"type"` + Value *Value `json:"value"` +} + +type HijackableResourceChain struct { + ID string `json:"id"` + Resource *HijackableResource `json:"resource"` + Upstream []*HijackableResource `json:"upstream"` + Downsteam []*HijackableResource `json:"downsteam"` +} + +type Type string + +const ( + TypeAccount Type = "ACCOUNT" + TypeZone Type = "ZONE" + TypeRecord Type = "RECORD" + TypeDistribution Type = "DISTRIBUTION" + TypeOrigin Type = "ORIGIN" +) + +var AllType = []Type{ + TypeAccount, + TypeZone, + TypeRecord, + TypeDistribution, + TypeOrigin, +} + +func (e Type) IsValid() bool { + switch e { + case TypeAccount, TypeZone, TypeRecord, TypeDistribution, TypeOrigin: + return true + } + return false +} + +func (e Type) String() string { + return string(e) +} + +func (e *Type) UnmarshalGQL(v interface{}) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enums must be strings") + } + + *e = Type(str) + if !e.IsValid() { + return fmt.Errorf("%s is not a valid Type", str) + } + return nil +} + +func (e Type) MarshalGQL(w io.Writer) { + fmt.Fprint(w, strconv.Quote(e.String())) +} diff --git a/cloud-inquisitor/graph/schema.graphqls b/cloud-inquisitor/graph/schema.graphqls index 1c1409b..bf60dcd 100644 --- a/cloud-inquisitor/graph/schema.graphqls +++ b/cloud-inquisitor/graph/schema.graphqls @@ -1,3 +1,11 @@ +enum Type { + ACCOUNT + ZONE + RECORD + DISTRIBUTION + ORIGIN +} + type Account { accountID: ID! zones: [Zone!]! @@ -42,6 +50,20 @@ type OriginGroup { origins: [Value!]! } +type HijackableResource { + id: ID! + account: String! + type: Type! + value: Value! +} + +type HijackableResourceChain { + id: ID! + resource: HijackableResource! + upstream: [HijackableResource!]! + downsteam: [HijackableResource!]! +} + type Query { accounts: [Account!]! account(id: ID!): Account! @@ -51,13 +73,24 @@ type Query { records: [Record!]! record(id: ID!): Record! + pointedAtByRecords(domain: String!): [Record!]! values: [Value!]! value(id: ID!): Value! distributions: [Distribution!]! distribution(id: ID!): Distribution! + pointedAtByDistribution(domain: String!): [Distribution!]! origins: [Origin!]! origin(id: ID!): Origin + pointedAtByOrigin(domain: String!): [Origin!]! + + originGroups: [OriginGroup!]! + pointedAtByOriginGroup(domain: String!): [OriginGroup!]! + + hijackChainByDomain(domain: String!): HijackableResourceChain! + + + getElasticbeanstalkUpstreamHijack(endpoints: [String!]!): [HijackableResource!]! } \ No newline at end of file diff --git a/cloud-inquisitor/graph/schema.resolvers.go b/cloud-inquisitor/graph/schema.resolvers.go index 0419b7a..6f6f438 100644 --- a/cloud-inquisitor/graph/schema.resolvers.go +++ b/cloud-inquisitor/graph/schema.resolvers.go @@ -5,8 +5,9 @@ package graph import ( "context" + "fmt" - generated1 "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/graph/generated" + "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/graph/generated" "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/graph/model" log "github.com/sirupsen/logrus" ) @@ -231,6 +232,10 @@ func (r *queryResolver) Record(ctx context.Context, id string) (*model.Record, e return &record, nil } +func (r *queryResolver) PointedAtByRecords(ctx context.Context, domain string) ([]*model.Record, error) { + panic(fmt.Errorf("not implemented")) +} + func (r *queryResolver) Values(ctx context.Context) ([]*model.Value, error) { log.Info("getting all values") var values []*model.Value @@ -291,6 +296,10 @@ func (r *queryResolver) Distribution(ctx context.Context, id string) (*model.Dis return &distro, nil } +func (r *queryResolver) PointedAtByDistribution(ctx context.Context, domain string) ([]*model.Distribution, error) { + panic(fmt.Errorf("not implemented")) +} + func (r *queryResolver) Origins(ctx context.Context) ([]*model.Origin, error) { log.Debugln("getting all origins") var origins []*model.Origin @@ -314,6 +323,26 @@ func (r *queryResolver) Origin(ctx context.Context, id string) (*model.Origin, e return &origin, nil } +func (r *queryResolver) PointedAtByOrigin(ctx context.Context, domain string) ([]*model.Origin, error) { + panic(fmt.Errorf("not implemented")) +} + +func (r *queryResolver) OriginGroups(ctx context.Context) ([]*model.OriginGroup, error) { + panic(fmt.Errorf("not implemented")) +} + +func (r *queryResolver) PointedAtByOriginGroup(ctx context.Context, domain string) ([]*model.OriginGroup, error) { + panic(fmt.Errorf("not implemented")) +} + +func (r *queryResolver) HijackChainByDomain(ctx context.Context, domain string) (*model.HijackableResourceChain, error) { + panic(fmt.Errorf("not implemented")) +} + +func (r *queryResolver) GetElasticbeanstalkUpstreamHijack(ctx context.Context, endpoints []string) ([]*model.HijackableResource, error) { + panic(fmt.Errorf("not implemented")) +} + func (r *recordResolver) Values(ctx context.Context, obj *model.Record) ([]*model.Value, error) { log.Infof("record <%v> getting values\n", obj.RecordID) log.Debugf("%#v\n", *obj) @@ -375,20 +404,20 @@ func (r *zoneResolver) Record(ctx context.Context, obj *model.Zone, id string) ( return &record, nil } -// Account returns generated1.AccountResolver implementation. -func (r *Resolver) Account() generated1.AccountResolver { return &accountResolver{r} } +// Account returns generated.AccountResolver implementation. +func (r *Resolver) Account() generated.AccountResolver { return &accountResolver{r} } -// Distribution returns generated1.DistributionResolver implementation. -func (r *Resolver) Distribution() generated1.DistributionResolver { return &distributionResolver{r} } +// Distribution returns generated.DistributionResolver implementation. +func (r *Resolver) Distribution() generated.DistributionResolver { return &distributionResolver{r} } -// Query returns generated1.QueryResolver implementation. -func (r *Resolver) Query() generated1.QueryResolver { return &queryResolver{r} } +// Query returns generated.QueryResolver implementation. +func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} } -// Record returns generated1.RecordResolver implementation. -func (r *Resolver) Record() generated1.RecordResolver { return &recordResolver{r} } +// Record returns generated.RecordResolver implementation. +func (r *Resolver) Record() generated.RecordResolver { return &recordResolver{r} } -// Zone returns generated1.ZoneResolver implementation. -func (r *Resolver) Zone() generated1.ZoneResolver { return &zoneResolver{r} } +// Zone returns generated.ZoneResolver implementation. +func (r *Resolver) Zone() generated.ZoneResolver { return &zoneResolver{r} } type accountResolver struct{ *Resolver } type distributionResolver struct{ *Resolver } diff --git a/cloud-inquisitor/resource.go b/cloud-inquisitor/resource.go index 470e15a..92ee05d 100644 --- a/cloud-inquisitor/resource.go +++ b/cloud-inquisitor/resource.go @@ -2,7 +2,6 @@ package cloudinquisitor import ( "context" - "encoding/json" "errors" log "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/logger" @@ -71,15 +70,6 @@ type TaggableResource interface { GetMissingTags() []string } -type HijackableResource interface { - Resource - NewFromEventBus(events.CloudWatchEvent, context.Context, map[string]interface{}) error - NewFromPassableResource(PassableResource, context.Context, map[string]interface{}) error - // PublishState is provided to give an easy hook to - // send and store struct state in a backend data store - PublishState() error -} - type PassableResource struct { Resource interface{} Type Service @@ -106,33 +96,6 @@ func (p PassableResource) GetTaggableResource(ctx context.Context, metadata map[ } } -func (p PassableResource) GetHijackableResource(ctx context.Context, metadata map[string]interface{}) (HijackableResource, error) { - switch p.Type { - case SERVICE_STUB: - stub := &StubResource{} - err := stub.NewFromPassableResource(p, ctx, metadata) - return stub, err - case SERVICE_AWS_ROUTE53_ZONE: - r53 := &AWSRoute53Zone{} - err := r53.NewFromPassableResource(p, ctx, metadata) - return r53, err - case SERVICE_AWS_ROUTE53_RECORD: - r53 := &AWSRoute53Record{} - err := r53.NewFromPassableResource(p, ctx, metadata) - return r53, err - case SERVICE_AWS_CLOUDFRONT: - cf := &AWSCloudFrontDistributionHijackableResource{} - err := cf.NewFromPassableResource(p, ctx, metadata) - return cf, err - case SERVICE_AWS_ELASTICBEANSTALK: - eb := &AWSElasticBeanstalkEnvironmentHijackableResource{} - err := eb.NewFromPassableResource(p, ctx, metadata) - return eb, err - default: - return nil, errors.New("no matching resource for type " + p.Type) - } -} - func NewTaggableResource(event events.CloudWatchEvent, ctx context.Context, metadata map[string]interface{}) (TaggableResource, error) { var resource TaggableResource = nil switch event.Source { @@ -159,63 +122,3 @@ func NewTaggableResource(event events.CloudWatchEvent, ctx context.Context, meta } return resource, nil } - -func NewHijackableResource(event events.CloudWatchEvent, ctx context.Context, metadata map[string]interface{}) (HijackableResource, error) { - var resource HijackableResource = nil - switch event.Source { - case "aws.route53": - detailMap := map[string]interface{}{} - err := json.Unmarshal(event.Detail, &detailMap) - if err != nil { - return resource, err - } - if eventName, ok := detailMap["eventName"]; ok { - switch eventName { - case "CreateHostedZone": - resource = &AWSRoute53Zone{} - resourceErr := resource.NewFromEventBus(event, ctx, metadata) - return resource, resourceErr - case "ChangeResourceRecordSets": - resource = &AWSRoute53RecordSet{} - resourceErr := resource.NewFromEventBus(event, ctx, metadata) - return resource, resourceErr - default: - resource = &StubResource{} - return resource, errors.New("unknown route53 eventName") - } - } else { - resource = &StubResource{} - return resource, errors.New("unable to parse evetName from map") - } - case "aws.cloudfront": - resource = &AWSCloudFrontDistributionHijackableResource{} - resourceErr := resource.NewFromEventBus(event, ctx, metadata) - return resource, resourceErr - case "aws.elasticbeanstalk": - detailMap := map[string]interface{}{} - err := json.Unmarshal(event.Detail, &detailMap) - if err != nil { - return resource, err - } - if eventName, ok := detailMap["eventName"]; ok { - switch eventName { - case "TerminateEnvironment": - resource = &AWSElasticBeanstalkEnvironmentHijackableResource{} - resourceErr := resource.NewFromEventBus(event, ctx, metadata) - return resource, resourceErr - default: - resource = &StubResource{} - return resource, errors.New("unknown route53 eventName") - } - } else { - resource = &StubResource{} - return resource, errors.New("unable to parse evetName from map") - } - default: - resource = &StubResource{} - err := resource.NewFromEventBus(event, ctx, metadata) - return resource, err - - } - return resource, nil -} diff --git a/cloud-inquisitor/resource_hijackable.go b/cloud-inquisitor/resource_hijackable.go new file mode 100644 index 0000000..127a6eb --- /dev/null +++ b/cloud-inquisitor/resource_hijackable.go @@ -0,0 +1,119 @@ +package cloudinquisitor + +import ( + "context" + "encoding/json" + "errors" + + "github.com/aws/aws-lambda-go/events" + //"github.com/sirupsen/logrus" +) + +type HijackableResource interface { + Resource + NewFromEventBus(events.CloudWatchEvent, context.Context, map[string]interface{}) error + NewFromPassableResource(PassableResource, context.Context, map[string]interface{}) error + AnalyzeForHijack() (HijackChain, error) + // PublishState is provided to give an easy hook to + // send and store struct state in a backend data store + PublishState() error +} + +func (p PassableResource) GetHijackableResource(ctx context.Context, metadata map[string]interface{}) (HijackableResource, error) { + switch p.Type { + case SERVICE_STUB: + stub := &StubResource{} + err := stub.NewFromPassableResource(p, ctx, metadata) + return stub, err + case SERVICE_AWS_ROUTE53_ZONE: + r53 := &AWSRoute53Zone{} + err := r53.NewFromPassableResource(p, ctx, metadata) + return r53, err + case SERVICE_AWS_ROUTE53_RECORD: + r53 := &AWSRoute53Record{} + err := r53.NewFromPassableResource(p, ctx, metadata) + return r53, err + case SERVICE_AWS_CLOUDFRONT: + cf := &AWSCloudFrontDistributionHijackableResource{} + err := cf.NewFromPassableResource(p, ctx, metadata) + return cf, err + case SERVICE_AWS_ELASTICBEANSTALK: + eb := &AWSElasticBeanstalkEnvironmentHijackableResource{} + err := eb.NewFromPassableResource(p, ctx, metadata) + return eb, err + default: + return nil, errors.New("no matching resource for type " + p.Type) + } +} + +func NewHijackableResource(event events.CloudWatchEvent, ctx context.Context, metadata map[string]interface{}) (HijackableResource, error) { + var resource HijackableResource = nil + switch event.Source { + case "aws.route53": + detailMap := map[string]interface{}{} + err := json.Unmarshal(event.Detail, &detailMap) + if err != nil { + return resource, err + } + if eventName, ok := detailMap["eventName"]; ok { + switch eventName { + case "CreateHostedZone": + resource = &AWSRoute53Zone{} + resourceErr := resource.NewFromEventBus(event, ctx, metadata) + return resource, resourceErr + case "ChangeResourceRecordSets": + resource = &AWSRoute53RecordSet{} + resourceErr := resource.NewFromEventBus(event, ctx, metadata) + return resource, resourceErr + default: + resource = &StubResource{} + return resource, errors.New("unknown route53 eventName") + } + } else { + resource = &StubResource{} + return resource, errors.New("unable to parse evetName from map") + } + case "aws.cloudfront": + resource = &AWSCloudFrontDistributionHijackableResource{} + resourceErr := resource.NewFromEventBus(event, ctx, metadata) + return resource, resourceErr + case "aws.elasticbeanstalk": + detailMap := map[string]interface{}{} + err := json.Unmarshal(event.Detail, &detailMap) + if err != nil { + return resource, err + } + if eventName, ok := detailMap["eventName"]; ok { + switch eventName { + case "TerminateEnvironment": + resource = &AWSElasticBeanstalkEnvironmentHijackableResource{} + resourceErr := resource.NewFromEventBus(event, ctx, metadata) + return resource, resourceErr + default: + resource = &StubResource{} + return resource, errors.New("unknown route53 eventName") + } + } else { + resource = &StubResource{} + return resource, errors.New("unable to parse evetName from map") + } + default: + resource = &StubResource{} + err := resource.NewFromEventBus(event, ctx, metadata) + return resource, err + + } + return resource, nil +} + +type HijackChain struct { + Chain []HijackChainElement +} + +type HijackChainElement struct { + AccountId string + Resource string + ResourceType string + ResourceReferenced string + ResourceReferencedType string +} diff --git a/cloud-inquisitor/serverless/hijack_graph_analyzer/main.go b/cloud-inquisitor/serverless/hijack_graph_analyzer/main.go new file mode 100644 index 0000000..421c67a --- /dev/null +++ b/cloud-inquisitor/serverless/hijack_graph_analyzer/main.go @@ -0,0 +1,70 @@ +package main + +import ( + "context" + "errors" + "reflect" + + cloudinquisitor "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor" + "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/instrumentation/newrelic" + "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/settings" +) + +func handlerRequest(ctx context.Context, resource cloudinquisitor.PassableResource) (cloudinquisitor.PassableResource, error) { + + parsedResource, err := resource.GetHijackableResource(ctx, map[string]interface{}{ + "cloud-inquisitor-component": "hijack-graph-updater", + }) + if err != nil { + return resource, err + } + + err = parsedResource.RefreshState() + if err != nil { + return resource, err + } + + hijackChain, err := parsedResource.AnalyzeForHijack() + if err != nil { + return cloudinquisitor.PassableResource{ + Resource: parsedResource, + Type: parsedResource.GetType(), + Metadata: parsedResource.GetLogger().GetMetadata(), + Finished: true, + }, err + } + + if reflect.DeepEqual((cloudinquisitor.HijackChain{}), hijackChain) { + return cloudinquisitor.PassableResource{ + Resource: parsedResource, + Type: parsedResource.GetType(), + Metadata: parsedResource.GetLogger().GetMetadata(), + Finished: true, + }, errors.New("hijack analysis returned nil hijack chain") + } + + if len(hijackChain.Chain) > 0 { + // send notification + } + + err = parsedResource.PublishState() + if err != nil { + return cloudinquisitor.PassableResource{ + Resource: parsedResource, + Type: parsedResource.GetType(), + Metadata: parsedResource.GetLogger().GetMetadata(), + Finished: true, + }, err + } + + return cloudinquisitor.PassableResource{ + Resource: parsedResource, + Type: parsedResource.GetType(), + Metadata: parsedResource.GetLogger().GetMetadata(), + Finished: true, + }, nil +} + +func main() { + newrelic.StartNewRelicLambda(handlerRequest, settings.GetString("name")) +} diff --git a/cloud-inquisitor/serverless/hijack_initializer/main.go b/cloud-inquisitor/serverless/hijack_initializer/main.go index 1b818e5..0df597f 100644 --- a/cloud-inquisitor/serverless/hijack_initializer/main.go +++ b/cloud-inquisitor/serverless/hijack_initializer/main.go @@ -48,7 +48,7 @@ func handlerRequest(ctx context.Context, event events.CloudWatchEvent) (passable Metadata: record.GetLogger().GetMetadata(), }) } - case cloudinquisitor.SERVICE_AWS_ROUTE53_ZONE, cloudinquisitor.SERVICE_AWS_CLOUDFRONT: + case cloudinquisitor.SERVICE_AWS_ROUTE53_ZONE, cloudinquisitor.SERVICE_AWS_CLOUDFRONT, cloudinquisitor.SERVICE_AWS_ELASTICBEANSTALK: passableResources = append(passableResources, cloudinquisitor.PassableResource{ Resource: resource, Type: resource.GetType(), diff --git a/cloud-inquisitor/stub_resource.go b/cloud-inquisitor/stub_resource.go index e3f6ade..573fb03 100644 --- a/cloud-inquisitor/stub_resource.go +++ b/cloud-inquisitor/stub_resource.go @@ -5,7 +5,7 @@ import ( "encoding/json" "errors" - "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/instrumentation" + instrument "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/instrumentation" log "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/logger" "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/settings" "github.com/aws/aws-lambda-go/events" @@ -114,3 +114,7 @@ func (t *StubResource) GetMissingTags() []string { func (t *StubResource) GetTags() map[string]string { return map[string]string{} } + +func (t *StubResource) AnalyzeForHijack() (HijackChain, error) { + return HijackChain{[]HijackChainElement{}}, nil +} diff --git a/go.mod b/go.mod index 5a071c5..815a68f 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/newrelic/go-agent/v3/integrations/logcontext/nrlogrusplugin v1.0.0 github.com/newrelic/go-agent/v3/integrations/nrlambda v1.1.0 github.com/pelletier/go-toml v1.8.0 // indirect - github.com/rogpeppe/go-internal v1.6.1 // indirect + github.com/rogpeppe/go-internal v1.6.2 // indirect github.com/sirupsen/logrus v1.6.0 github.com/spf13/afero v1.3.0 // indirect github.com/spf13/cast v1.3.1 // indirect @@ -34,7 +34,7 @@ require ( golang.org/x/net v0.0.0-20200625001655-4c5254603344 // indirect golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 // indirect - golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a // indirect + golang.org/x/sys v0.0.0-20200908134130-d2e65c121b96 // indirect golang.org/x/text v0.3.3 // indirect google.golang.org/appengine v1.6.6 // indirect google.golang.org/genproto v0.0.0-20200626011028-ee7919e894b5 // indirect diff --git a/go.sum b/go.sum index 75bc4b4..6f18ea9 100644 --- a/go.sum +++ b/go.sum @@ -339,6 +339,8 @@ github.com/rogpeppe/go-internal v1.6.0 h1:IZRgg4sfrDH7nsAD1Y/Nwj+GzIfEwpJSLjCaNC github.com/rogpeppe/go-internal v1.6.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.6.2 h1:aIihoIOHCiLZHxyoNQ+ABL4NKhFTgKLBdMLyEAh98m0= +github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= @@ -535,6 +537,8 @@ golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6 h1:DvY3Zkh7KabQE/kfzMvYvKirS golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a h1:i47hUS795cOydZI4AwJQCKXOr4BvxzvikwDoDtHhP2Y= golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200908134130-d2e65c121b96 h1:gJciq3lOg0eS9fSZJcoHfv7q1BfC6cJfnmSSKL1yu3Q= +golang.org/x/sys v0.0.0-20200908134130-d2e65c121b96/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= diff --git a/terraform_modules/event_rule/event_rule.tf b/terraform_modules/event_rule/event_rule.tf index 2448ec4..16bccb6 100644 --- a/terraform_modules/event_rule/event_rule.tf +++ b/terraform_modules/event_rule/event_rule.tf @@ -3,6 +3,7 @@ locals { { "route53_dns_hijacks" = file("${path.module}/patterns/route53_dns_hijacks.json") "cloudfront_dns_hijacks" = file("${path.module}/patterns/cloudfront_dns_hijacks.json") + "elasticbeanstalk_dns_hijacks" = file("${path.module}/patterns/elasticbeanstalk_dns_hijacks.json") } ) } diff --git a/terraform_modules/event_rule/patterns/elastic_beanstalk_dns_hijacks.json b/terraform_modules/event_rule/patterns/elasticbeanstalk_dns_hijacks.json similarity index 100% rename from terraform_modules/event_rule/patterns/elastic_beanstalk_dns_hijacks.json rename to terraform_modules/event_rule/patterns/elasticbeanstalk_dns_hijacks.json diff --git a/terraform_modules/step_function/step_function_definitions/domain_hijack.tpl b/terraform_modules/step_function/step_function_definitions/domain_hijack.tpl index 776a636..7156162 100644 --- a/terraform_modules/step_function/step_function_definitions/domain_hijack.tpl +++ b/terraform_modules/step_function/step_function_definitions/domain_hijack.tpl @@ -68,6 +68,15 @@ } ], "Next": "Track and Analyze for Hijack" + }, + { + "Or":[ + { + "Variable": "$.Resource.EventName", + "StringEquals": "TerminateEnvironment" + } + ], + "Next": "Analyze for Hijack and Remove From Graph" } ], "Default": "End of iterator" @@ -77,6 +86,11 @@ "Resource": "${graph_updater}", "End": true }, + "Analyze for Hijack and Remove From Graph": { + "Type": "Task", + "Resource": "${graph_analyzer}", + "End": true + }, "Track and Analyze for Hijack": { "Type": "Parallel", "End": true, diff --git a/terraform_modules/step_function/step_functions.tf b/terraform_modules/step_function/step_functions.tf index 50a3d24..22c0ec3 100644 --- a/terraform_modules/step_function/step_functions.tf +++ b/terraform_modules/step_function/step_functions.tf @@ -31,7 +31,8 @@ data "template_file" "dns_hijack" { template = "${file("${path.module}/step_function_definitions/domain_hijack.tpl")}" vars = merge({ "init": "", - "graph_updater": "" + "graph_updater": "", + "graph_analyzer": "", }, local.lambda_arns) } \ No newline at end of file From d28bada73d935715e260a154a5eb188f164e144b Mon Sep 17 00:00:00 2001 From: Jesse Taylor Date: Tue, 8 Sep 2020 20:20:17 -0700 Subject: [PATCH 3/9] adding in queries to look up resources by domain --- cloud-inquisitor/graph/schema.resolvers.go | 114 +++++++++++++++++++-- cmd/dns_hijack_http.go | 8 ++ 2 files changed, 111 insertions(+), 11 deletions(-) diff --git a/cloud-inquisitor/graph/schema.resolvers.go b/cloud-inquisitor/graph/schema.resolvers.go index 6f6f438..a4c41c4 100644 --- a/cloud-inquisitor/graph/schema.resolvers.go +++ b/cloud-inquisitor/graph/schema.resolvers.go @@ -233,7 +233,33 @@ func (r *queryResolver) Record(ctx context.Context, id string) (*model.Record, e } func (r *queryResolver) PointedAtByRecords(ctx context.Context, domain string) ([]*model.Record, error) { - panic(fmt.Errorf("not implemented")) + log.Infof("finding all records that point to %v", domain) + var recordValues []*model.Value + err := r.DB.Where(model.Value{ValueID: domain}).Find(&recordValues).Error + if err != nil { + return []*model.Record{}, err + } + + recordIds := []uint{} + for _, val := range recordValues { + if val.RecordID != 0 { + recordIds = append(recordIds, val.RecordID) + } + } + + var records []*model.Record + err = r.DB.Find(&records, recordIds).Error + if err != nil { + return []*model.Record{}, err + } + + if log.GetLevel() == log.DebugLevel { + for _, record := range records { + log.Debugf("record %#v points to domain %v", record, domain) + } + } + + return records, nil } func (r *queryResolver) Values(ctx context.Context) ([]*model.Value, error) { @@ -297,7 +323,39 @@ func (r *queryResolver) Distribution(ctx context.Context, id string) (*model.Dis } func (r *queryResolver) PointedAtByDistribution(ctx context.Context, domain string) ([]*model.Distribution, error) { - panic(fmt.Errorf("not implemented")) + log.Infof("finding all distributions pointing at domain %v", domain) + distroIds := []uint{} + origins, err := r.PointedAtByOrigin(ctx, domain) + if err != nil { + log.Errorf("erroring getting origins for distribution: %v", err.Error()) + } else { + for _, origin := range origins { + distroIds = append(distroIds, origin.DistributionID) + } + } + + originGroups, err := r.PointedAtByOriginGroup(ctx, domain) + if err != nil { + log.Errorf("error getting origin groups for distribution: %v", err.Error()) + } else { + for _, origin := range originGroups { + distroIds = append(distroIds, origin.DistributionID) + } + } + + var distributions []*model.Distribution + err = r.DB.Find(&distributions, distroIds).Error + if err != nil { + return []*model.Distribution{}, nil + } + + if log.GetLevel() == log.DebugLevel { + for _, distro := range distributions { + log.Debugf("found distribution %v for domain %v", *distro, domain) + } + } + + return distributions, nil } func (r *queryResolver) Origins(ctx context.Context) ([]*model.Origin, error) { @@ -324,15 +382,53 @@ func (r *queryResolver) Origin(ctx context.Context, id string) (*model.Origin, e } func (r *queryResolver) PointedAtByOrigin(ctx context.Context, domain string) ([]*model.Origin, error) { - panic(fmt.Errorf("not implemented")) + log.Infof("looking for all origins pointing at dominan %v", domain) + var origins []*model.Origin + err := r.DB.Where(model.Origin{Domain: domain}).Find(&origins).Error + if err != nil { + return []*model.Origin{}, err + } + + return origins, nil } func (r *queryResolver) OriginGroups(ctx context.Context) ([]*model.OriginGroup, error) { - panic(fmt.Errorf("not implemented")) + var originGroups []*model.OriginGroup + err := r.DB.Preload("Origins").Find(&originGroups).Error + if err != nil { + return []*model.OriginGroup{}, err + } + + return originGroups, nil } func (r *queryResolver) PointedAtByOriginGroup(ctx context.Context, domain string) ([]*model.OriginGroup, error) { - panic(fmt.Errorf("not implemented")) + log.Infof("looking for origin groups pointing at domain %v", domain) + + var domainValues []*model.Value + err := r.DB.Where(model.Value{ValueID: domain}).Find(&domainValues).Error + if err != nil { + return []*model.OriginGroup{}, err + } + + originGroupIds := []uint{} + for _, val := range domainValues { + originGroupIds = append(originGroupIds, val.OriginGroupID) + } + + var originGroups []*model.OriginGroup + err = r.DB.Find(&originGroups, originGroupIds).Error + if err != nil { + return []*model.OriginGroup{}, err + } + + if log.GetLevel() == log.DebugLevel { + for _, group := range originGroups { + log.Debugf("origin group %v pointing to domain %v", group, domain) + } + } + + return originGroups, nil } func (r *queryResolver) HijackChainByDomain(ctx context.Context, domain string) (*model.HijackableResourceChain, error) { @@ -346,18 +442,14 @@ func (r *queryResolver) GetElasticbeanstalkUpstreamHijack(ctx context.Context, e func (r *recordResolver) Values(ctx context.Context, obj *model.Record) ([]*model.Value, error) { log.Infof("record <%v> getting values\n", obj.RecordID) log.Debugf("%#v\n", *obj) - db, err := NewDBConnection() - if err != nil { - return []*model.Value{}, err - } record := model.Record{RecordID: obj.RecordID} - err = db.Where(&record).First(&record).Error + err := r.DB.Where(&record).First(&record).Error if err != nil { return []*model.Value{}, err } var values []*model.Value - err = db.Model(&record).Association("ValueRelation").Find(&values).Error + err = r.DB.Model(&record).Association("ValueRelation").Find(&values).Error if err != nil { return []*model.Value{}, err } diff --git a/cmd/dns_hijack_http.go b/cmd/dns_hijack_http.go index 45b3277..bdd9e46 100644 --- a/cmd/dns_hijack_http.go +++ b/cmd/dns_hijack_http.go @@ -2,6 +2,7 @@ package main import ( "net/http" + "time" "github.com/99designs/gqlgen/graphql/handler" "github.com/99designs/gqlgen/graphql/playground" @@ -30,7 +31,14 @@ var dnsHijackHTTP = &cobra.Command{ panic(err) } defer db.Close() + // SetMaxIdleConns sets the maximum number of connections in the idle connection pool. + db.DB().SetMaxIdleConns(10) + // SetMaxOpenConns sets the maximum number of open connections to the database. + db.DB().SetMaxOpenConns(100) + + // SetConnMaxLifetime sets the maximum amount of time a connection may be reused. + db.DB().SetConnMaxLifetime(time.Hour) srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{ DB: db, }})) From 0ec663421a377d9f8913c26abd72ed60dce8f584 Mon Sep 17 00:00:00 2001 From: Jesse Taylor Date: Wed, 9 Sep 2020 16:32:28 -0700 Subject: [PATCH 4/9] adds in all the query login to get all resources that could front an elastic beanstalk --- cloud-inquisitor/graph/generated/generated.go | 50 +++++---- cloud-inquisitor/graph/model/models_gen.go | 14 ++- cloud-inquisitor/graph/resolver.go | 23 ++++ cloud-inquisitor/graph/schema.graphqls | 8 +- cloud-inquisitor/graph/schema.resolvers.go | 104 +++++++++++++++++- 5 files changed, 165 insertions(+), 34 deletions(-) diff --git a/cloud-inquisitor/graph/generated/generated.go b/cloud-inquisitor/graph/generated/generated.go index 2ff9a73..91b325a 100644 --- a/cloud-inquisitor/graph/generated/generated.go +++ b/cloud-inquisitor/graph/generated/generated.go @@ -91,7 +91,7 @@ type ComplexityRoot struct { Distribution func(childComplexity int, id string) int Distributions func(childComplexity int) int GetElasticbeanstalkUpstreamHijack func(childComplexity int, endpoints []string) int - HijackChainByDomain func(childComplexity int, domain string) int + HijackChainByDomain func(childComplexity int, domain string, typeArg model.Type) int Origin func(childComplexity int, id string) int OriginGroups func(childComplexity int) int Origins func(childComplexity int) int @@ -155,8 +155,8 @@ type QueryResolver interface { PointedAtByOrigin(ctx context.Context, domain string) ([]*model.Origin, error) OriginGroups(ctx context.Context) ([]*model.OriginGroup, error) PointedAtByOriginGroup(ctx context.Context, domain string) ([]*model.OriginGroup, error) - HijackChainByDomain(ctx context.Context, domain string) (*model.HijackableResourceChain, error) GetElasticbeanstalkUpstreamHijack(ctx context.Context, endpoints []string) ([]*model.HijackableResource, error) + HijackChainByDomain(ctx context.Context, domain string, typeArg model.Type) (*model.HijackableResourceChain, error) } type RecordResolver interface { Values(ctx context.Context, obj *model.Record) ([]*model.Value, error) @@ -393,7 +393,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return 0, false } - return e.complexity.Query.HijackChainByDomain(childComplexity, args["domain"].(string)), true + return e.complexity.Query.HijackChainByDomain(childComplexity, args["domain"].(string), args["type"].(model.Type)), true case "Query.origin": if e.complexity.Query.Origin == nil { @@ -657,6 +657,7 @@ var sources = []*ast.Source{ RECORD DISTRIBUTION ORIGIN + ELASTICBEANSTALK } type Account { @@ -727,6 +728,7 @@ type Query { records: [Record!]! record(id: ID!): Record! pointedAtByRecords(domain: String!): [Record!]! + values: [Value!]! value(id: ID!): Value! @@ -742,10 +744,10 @@ type Query { originGroups: [OriginGroup!]! pointedAtByOriginGroup(domain: String!): [OriginGroup!]! - hijackChainByDomain(domain: String!): HijackableResourceChain! - - getElasticbeanstalkUpstreamHijack(endpoints: [String!]!): [HijackableResource!]! + + hijackChainByDomain(domain: String!, type: Type!): HijackableResourceChain! + }`, BuiltIn: false}, } var parsedSchema = gqlparser.MustLoadSchema(sources...) @@ -835,6 +837,14 @@ func (ec *executionContext) field_Query_hijackChainByDomain_args(ctx context.Con } } args["domain"] = arg0 + var arg1 model.Type + if tmp, ok := rawArgs["type"]; ok { + arg1, err = ec.unmarshalNType2githubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐType(ctx, tmp) + if err != nil { + return nil, err + } + } + args["type"] = arg1 return args, nil } @@ -2366,7 +2376,7 @@ func (ec *executionContext) _Query_pointedAtByOriginGroup(ctx context.Context, f return ec.marshalNOriginGroup2ᚕᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐOriginGroupᚄ(ctx, field.Selections, res) } -func (ec *executionContext) _Query_hijackChainByDomain(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { +func (ec *executionContext) _Query_getElasticbeanstalkUpstreamHijack(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -2382,7 +2392,7 @@ func (ec *executionContext) _Query_hijackChainByDomain(ctx context.Context, fiel ctx = graphql.WithFieldContext(ctx, fc) rawArgs := field.ArgumentMap(ec.Variables) - args, err := ec.field_Query_hijackChainByDomain_args(ctx, rawArgs) + args, err := ec.field_Query_getElasticbeanstalkUpstreamHijack_args(ctx, rawArgs) if err != nil { ec.Error(ctx, err) return graphql.Null @@ -2390,7 +2400,7 @@ func (ec *executionContext) _Query_hijackChainByDomain(ctx context.Context, fiel fc.Args = args resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().HijackChainByDomain(rctx, args["domain"].(string)) + return ec.resolvers.Query().GetElasticbeanstalkUpstreamHijack(rctx, args["endpoints"].([]string)) }) if err != nil { ec.Error(ctx, err) @@ -2402,12 +2412,12 @@ func (ec *executionContext) _Query_hijackChainByDomain(ctx context.Context, fiel } return graphql.Null } - res := resTmp.(*model.HijackableResourceChain) + res := resTmp.([]*model.HijackableResource) fc.Result = res - return ec.marshalNHijackableResourceChain2ᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐHijackableResourceChain(ctx, field.Selections, res) + return ec.marshalNHijackableResource2ᚕᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐHijackableResourceᚄ(ctx, field.Selections, res) } -func (ec *executionContext) _Query_getElasticbeanstalkUpstreamHijack(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { +func (ec *executionContext) _Query_hijackChainByDomain(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -2423,7 +2433,7 @@ func (ec *executionContext) _Query_getElasticbeanstalkUpstreamHijack(ctx context ctx = graphql.WithFieldContext(ctx, fc) rawArgs := field.ArgumentMap(ec.Variables) - args, err := ec.field_Query_getElasticbeanstalkUpstreamHijack_args(ctx, rawArgs) + args, err := ec.field_Query_hijackChainByDomain_args(ctx, rawArgs) if err != nil { ec.Error(ctx, err) return graphql.Null @@ -2431,7 +2441,7 @@ func (ec *executionContext) _Query_getElasticbeanstalkUpstreamHijack(ctx context fc.Args = args resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().GetElasticbeanstalkUpstreamHijack(rctx, args["endpoints"].([]string)) + return ec.resolvers.Query().HijackChainByDomain(rctx, args["domain"].(string), args["type"].(model.Type)) }) if err != nil { ec.Error(ctx, err) @@ -2443,9 +2453,9 @@ func (ec *executionContext) _Query_getElasticbeanstalkUpstreamHijack(ctx context } return graphql.Null } - res := resTmp.([]*model.HijackableResource) + res := resTmp.(*model.HijackableResourceChain) fc.Result = res - return ec.marshalNHijackableResource2ᚕᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐHijackableResourceᚄ(ctx, field.Selections, res) + return ec.marshalNHijackableResourceChain2ᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐHijackableResourceChain(ctx, field.Selections, res) } func (ec *executionContext) _Query___type(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { @@ -4468,7 +4478,7 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr } return res }) - case "hijackChainByDomain": + case "getElasticbeanstalkUpstreamHijack": field := field out.Concurrently(i, func() (res graphql.Marshaler) { defer func() { @@ -4476,13 +4486,13 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr ec.Error(ctx, ec.Recover(ctx, r)) } }() - res = ec._Query_hijackChainByDomain(ctx, field) + res = ec._Query_getElasticbeanstalkUpstreamHijack(ctx, field) if res == graphql.Null { atomic.AddUint32(&invalids, 1) } return res }) - case "getElasticbeanstalkUpstreamHijack": + case "hijackChainByDomain": field := field out.Concurrently(i, func() (res graphql.Marshaler) { defer func() { @@ -4490,7 +4500,7 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr ec.Error(ctx, ec.Recover(ctx, r)) } }() - res = ec._Query_getElasticbeanstalkUpstreamHijack(ctx, field) + res = ec._Query_hijackChainByDomain(ctx, field) if res == graphql.Null { atomic.AddUint32(&invalids, 1) } diff --git a/cloud-inquisitor/graph/model/models_gen.go b/cloud-inquisitor/graph/model/models_gen.go index 7a9f81e..741f318 100644 --- a/cloud-inquisitor/graph/model/models_gen.go +++ b/cloud-inquisitor/graph/model/models_gen.go @@ -25,11 +25,12 @@ type HijackableResourceChain struct { type Type string const ( - TypeAccount Type = "ACCOUNT" - TypeZone Type = "ZONE" - TypeRecord Type = "RECORD" - TypeDistribution Type = "DISTRIBUTION" - TypeOrigin Type = "ORIGIN" + TypeAccount Type = "ACCOUNT" + TypeZone Type = "ZONE" + TypeRecord Type = "RECORD" + TypeDistribution Type = "DISTRIBUTION" + TypeOrigin Type = "ORIGIN" + TypeElasticbeanstalk Type = "ELASTICBEANSTALK" ) var AllType = []Type{ @@ -38,11 +39,12 @@ var AllType = []Type{ TypeRecord, TypeDistribution, TypeOrigin, + TypeElasticbeanstalk, } func (e Type) IsValid() bool { switch e { - case TypeAccount, TypeZone, TypeRecord, TypeDistribution, TypeOrigin: + case TypeAccount, TypeZone, TypeRecord, TypeDistribution, TypeOrigin, TypeElasticbeanstalk: return true } return false diff --git a/cloud-inquisitor/graph/resolver.go b/cloud-inquisitor/graph/resolver.go index c4e4c20..6606946 100644 --- a/cloud-inquisitor/graph/resolver.go +++ b/cloud-inquisitor/graph/resolver.go @@ -7,6 +7,7 @@ package graph import ( "errors" "strings" + "time" "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/graph/model" "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/secrets/vault" @@ -161,3 +162,25 @@ func MigrateTables() error { type Resolver struct { DB *gorm.DB } + +func NewResolver() (*Resolver, error) { + db, err := graph.NewDBConnection() + if err != nil { + db.Close() + return nil, err + } + // SetMaxIdleConns sets the maximum number of connections in the idle connection pool. + db.DB().SetMaxIdleConns(10) + + // SetMaxOpenConns sets the maximum number of open connections to the database. + db.DB().SetMaxOpenConns(100) + + // SetConnMaxLifetime sets the maximum amount of time a connection may be reused. + db.DB().SetConnMaxLifetime(time.Hour) + + return &Resolver{DB: db}, nil +} + +func (r *Resolver) Close() error { + return r.DB.Close() +} diff --git a/cloud-inquisitor/graph/schema.graphqls b/cloud-inquisitor/graph/schema.graphqls index bf60dcd..684daa0 100644 --- a/cloud-inquisitor/graph/schema.graphqls +++ b/cloud-inquisitor/graph/schema.graphqls @@ -4,6 +4,7 @@ enum Type { RECORD DISTRIBUTION ORIGIN + ELASTICBEANSTALK } type Account { @@ -74,6 +75,7 @@ type Query { records: [Record!]! record(id: ID!): Record! pointedAtByRecords(domain: String!): [Record!]! + values: [Value!]! value(id: ID!): Value! @@ -89,8 +91,8 @@ type Query { originGroups: [OriginGroup!]! pointedAtByOriginGroup(domain: String!): [OriginGroup!]! - hijackChainByDomain(domain: String!): HijackableResourceChain! - - getElasticbeanstalkUpstreamHijack(endpoints: [String!]!): [HijackableResource!]! + + hijackChainByDomain(domain: String!, type: Type!): HijackableResourceChain! + } \ No newline at end of file diff --git a/cloud-inquisitor/graph/schema.resolvers.go b/cloud-inquisitor/graph/schema.resolvers.go index a4c41c4..d225a81 100644 --- a/cloud-inquisitor/graph/schema.resolvers.go +++ b/cloud-inquisitor/graph/schema.resolvers.go @@ -5,7 +5,6 @@ package graph import ( "context" - "fmt" "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/graph/generated" "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/graph/model" @@ -431,12 +430,107 @@ func (r *queryResolver) PointedAtByOriginGroup(ctx context.Context, domain strin return originGroups, nil } -func (r *queryResolver) HijackChainByDomain(ctx context.Context, domain string) (*model.HijackableResourceChain, error) { - panic(fmt.Errorf("not implemented")) +func (r *queryResolver) GetElasticbeanstalkUpstreamHijack(ctx context.Context, endpoints []string) ([]*model.HijackableResource, error) { + // elasticbeanstalks are endpoints are only fronted by other resources + hijackChain := []*model.HijackableResource{} + searchEndpoints := endpoints + // need to check: + // 1. cloudfront + for _, endpoint := range searchEndpoints { + distributions, err := r.Query().PointedAtByDistribution(ctx, endpoint) + if err != nil { + log.Errorf("error looking up distributions for endpoint %v", endpoint) + continue + } + + for _, distro := range distributions { + completeDistro, err := r.Query().Distribution(ctx, distro.DistributionID) + if err != nil { + log.Errorf("error looking up distribution with id %v", distro.DistributionID) + continue + } + + var account model.Account + err = r.DB.First(&account, completeDistro.AccountID).Error + if err != nil { + log.Errorf("unable to get account for distribution %#v", *completeDistro) + continue + } + + hijackChain = append(hijackChain, &model.HijackableResource{ + ID: completeDistro.DistributionID, + Type: model.TypeDistribution, + Account: account.AccountID, + Value: &model.Value{ + ValueID: completeDistro.Domain, + }, + }) + } + } + + if log.GetLevel() == log.DebugLevel { + for _, resource := range hijackChain { + log.Debugf("resource: %#v", *resource) + } + } + + for _, resource := range hijackChain { + searchEndpoints = append(searchEndpoints, resource.Value.ValueID) + } + // 2. route53 + for _, endpoint := range searchEndpoints { + records, err := r.Query().PointedAtByRecords(ctx, endpoint) + if err != nil { + log.Errorf("unable to get records taht point to endpoint %v", endpoint) + continue + } + + for _, record := range records { + completeRecord, err := r.Query().Record(ctx, record.RecordID) + if err != nil { + log.Errorf("unable to get complete record for record id %v", record.RecordID) + continue + } + + var account model.Account + err = r.DB.First(&account, completeRecord.AccountID).Error + if err != nil { + log.Errorf("unable to get account for distribution %#v", *completeRecord) + continue + } + + hijackChain = append(hijackChain, &model.HijackableResource{ + ID: completeRecord.RecordID, + Type: model.TypeRecord, + Account: account.AccountID, + Value: &model.Value{ + ValueID: completeRecord.RecordID, + }, + }) + + } + } + + return hijackChain, nil } -func (r *queryResolver) GetElasticbeanstalkUpstreamHijack(ctx context.Context, endpoints []string) ([]*model.HijackableResource, error) { - panic(fmt.Errorf("not implemented")) +func (r *queryResolver) HijackChainByDomain(ctx context.Context, domain string, typeArg model.Type) (*model.HijackableResourceChain, error) { + switch typeArg { + case model.TypeElasticbeanstalk: + chain, err := r.Query().GetElasticbeanstalkUpstreamHijack(ctx, []string{domain}) + if err != nil { + log.Errorf("recieved error when querying for elastic beanstalk hijacks: %v", err.Error()) + } + + return &model.HijackableResourceChain{ + ID: domain, + Resource: &model.HijackableResource{}, + Upstream: chain, + Downsteam: []*model.HijackableResource{}, + }, err + } + + return &model.HijackableResourceChain{}, nil } func (r *recordResolver) Values(ctx context.Context, obj *model.Record) ([]*model.Value, error) { From 7cb1a8055910dd6abdb1eea1ccc9b8306d86a23d Mon Sep 17 00:00:00 2001 From: Jesse Taylor Date: Mon, 14 Sep 2020 10:28:26 -0700 Subject: [PATCH 5/9] restructured step function to be more simple since addition can also cause hijacks. Added elasticbeanstalks to graph. Added in query to see if elasticbeanstalks are hijackable per event. --- .../aws_elasticbeanstalk_resource.go | 189 +++++++- cloud-inquisitor/graph/generated/generated.go | 425 ++++++++++++++++++ .../model/elasticbeanstalk_environment.go | 14 + cloud-inquisitor/graph/resolver.go | 8 +- cloud-inquisitor/graph/schema.graphqls | 10 + cloud-inquisitor/graph/schema.resolvers.go | 18 + cloud-inquisitor/resource_hijackable.go | 4 +- .../serverless/hijack_graph_analyzer/main.go | 6 - .../serverless/hijack_graph_updater/main.go | 7 +- .../serverless/hijack_initializer/main.go | 5 + docs/Elasticbeanstalk.md | 27 ++ go.mod | 2 +- go.sum | 2 + .../elasticbeanstalk_dns_hijacks.json | 3 +- .../domain_hijack.tpl | 99 +--- 15 files changed, 707 insertions(+), 112 deletions(-) create mode 100644 cloud-inquisitor/graph/model/elasticbeanstalk_environment.go create mode 100644 docs/Elasticbeanstalk.md diff --git a/cloud-inquisitor/aws_elasticbeanstalk_resource.go b/cloud-inquisitor/aws_elasticbeanstalk_resource.go index 3a5ce38..63f5e73 100644 --- a/cloud-inquisitor/aws_elasticbeanstalk_resource.go +++ b/cloud-inquisitor/aws_elasticbeanstalk_resource.go @@ -5,7 +5,10 @@ import ( "encoding/json" "errors" "reflect" + "time" + "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/graph" + "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/graph/model" instrument "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/instrumentation" log "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/logger" "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/settings" @@ -39,7 +42,8 @@ type AWSElasticBeanstalkDetail struct { } type AWSElasticBeastalkRequestParameters struct { - EnvironmentId string `json:"environmentId"` + EnvironmentId string `json:"environmentId"` + EnvironmentName string `json:"environmentName"` } type AWSElasticBeanstalkResponseElement struct { /*Distribution struct { @@ -96,6 +100,21 @@ func (eb *AWSElasticBeanstalkEnvironmentResource) RefreshState() error { } svc := elasticbeanstalk.New(regionalSession) + + switch eb.EventName { + case "TerminateEnvironment": + return eb.refreshDeletedEnvironment(svc) + case "CreateEnvironment": + return eb.refreshCreatedEnvironment(svc) + default: + return errors.New("unkown elasticbeanstalk event to parse") + } + + return nil + +} + +func (eb *AWSElasticBeanstalkEnvironmentResource) refreshDeletedEnvironment(svc *elasticbeanstalk.ElasticBeanstalk) error { input := &elasticbeanstalk.DescribeEnvironmentsInput{ EnvironmentIds: []*string{ aws.String(eb.EnvironmentId), @@ -137,6 +156,66 @@ func (eb *AWSElasticBeanstalkEnvironmentResource) RefreshState() error { eb.CNAME = *foundEnv.CNAME eb.Endpoint = *foundEnv.EndpointURL eb.EnvironmentName = *foundEnv.EnvironmentName + eb.CNAME = *foundEnv.CNAME + + return nil +} + +func (eb *AWSElasticBeanstalkEnvironmentResource) refreshCreatedEnvironment(svc *elasticbeanstalk.ElasticBeanstalk) error { + input := &elasticbeanstalk.DescribeEnvironmentsInput{ + EnvironmentNames: []*string{ + aws.String(eb.EnvironmentName), + }, + IncludeDeleted: aws.Bool(true), + } + + isReady := false + var foundEnv *elasticbeanstalk.EnvironmentDescription = nil + for !isReady { + + result, err := svc.DescribeEnvironments(input) + if err != nil { + eb.logger.WithFields(logrus.Fields{ + "cloud-inquisitor-resource": "aws-elasticbeanstalk-environment", + "cloud-inquisitor-error": "DescribeEnvironments", + }).WithFields(eb.GetMetadata()).Error(err.Error()) + return err + } + + eb.GetLogger().Debugf("describe environments input %#v", *input) + eb.GetLogger().Debugf("describe environments result %#v", *result) + + for _, env := range result.Environments { + eb.GetLogger().Debugf("beanstalk environment: %#v", *env) + if *env.EnvironmentName == eb.EnvironmentName { + foundEnv = env + break + } + } + + if foundEnv == nil { + eb.logger.WithFields(logrus.Fields{ + "cloud-inquisitor-resource": "aws-elasticbeanstalk-environment", + "cloud-inquisitor-error": "DescribeEnvironments", + }).WithFields(eb.GetMetadata()).Error("could not find elasticbeanstalk environment") + return errors.New("could not find elasticbeanstalk environment") + } + + if *foundEnv.Status == "Ready" { + break + } + + foundEnv = nil + time.Sleep(time.Second * 1) + } + + eb.Status = *foundEnv.Status + eb.ApplicationName = *foundEnv.ApplicationName + eb.CNAME = *foundEnv.CNAME + eb.Endpoint = *foundEnv.EndpointURL + eb.EnvironmentName = *foundEnv.EnvironmentName + eb.EnvironmentId = *foundEnv.EnvironmentId + eb.CNAME = *foundEnv.CNAME return nil } @@ -215,6 +294,7 @@ func (eb *AWSElasticBeanstalkEnvironmentHijackableResource) NewFromEventBus(even eb.logger.WithFields(eb.GetMetadata()).Debugf("aws event detail response elements: %#v", ebDetails.ResponseElements) eb.logger.WithFields(eb.GetMetadata()).Debugf("aws event detail request parameters: %#v", ebDetails.RequestParamaters) eb.EnvironmentId = ebDetails.RequestParamaters.EnvironmentId + eb.EnvironmentName = ebDetails.RequestParamaters.EnvironmentName eb.EventName = ebDetails.EventName err = eb.RefreshState() @@ -266,9 +346,114 @@ func (eb *AWSElasticBeanstalkEnvironmentHijackableResource) NewFromPassableResou } func (eb *AWSElasticBeanstalkEnvironmentHijackableResource) PublishState() error { + switch eb.EventName { + case "TerminateEnvironment": + return eb.publishDeletedEnvironment() + case "CreateEnvironment": + return eb.publishCreatedEnvironment() + default: + return errors.New("unknown elasticbeastalk event to publish") + } + return nil +} + +func (eb *AWSElasticBeanstalkEnvironmentHijackableResource) publishCreatedEnvironment() error { + db, err := graph.NewDBConnection() + defer db.Close() + if err != nil { + eb.logger.WithFields(eb.GetMetadata()).Error(err.Error()) + return err + } + + var account model.Account + err = db.Where(model.Account{AccountID: eb.AccountID}).FirstOrCreate(&account).Error + if err != nil { + eb.GetLogger().Errorf("error getting account for elasticbeastalk %v: %v", eb.EnvironmentId, err.Error()) + return err + } + + env := model.ElasticbeanstalkEnvironment{ + AccountID: account.ID, + ApplicationName: eb.ApplicationName, + EnvironmentID: eb.EnvironmentId, + EnvironmentName: eb.EnvironmentName, + EnvironmentURL: eb.Endpoint, + CName: eb.CNAME, + Region: eb.Region, + } + err = db.Where(env).FirstOrCreate(&env).Error + if err != nil { + eb.GetLogger().Errorf("error creating elasticbeastalk environment %v: %v", eb.EnvironmentId, err.Error()) + return err + } + + return nil +} + +func (eb *AWSElasticBeanstalkEnvironmentHijackableResource) publishDeletedEnvironment() error { + db, err := graph.NewDBConnection() + defer db.Close() + if err != nil { + eb.logger.WithFields(eb.GetMetadata()).Error(err.Error()) + return err + } + + env := model.ElasticbeanstalkEnvironment{ + EnvironmentID: eb.EnvironmentId, + EnvironmentName: eb.EnvironmentName, + ApplicationName: eb.ApplicationName, + EnvironmentURL: eb.Endpoint, + CName: eb.CNAME, + Region: eb.Region, + } + + err = db.Delete(&env).Error + if err != nil { + eb.GetLogger().Errorf("error removing elasticbeanstalk %v: %v", eb.EnvironmentId, err.Error) + return err + } + return nil } func (eb *AWSElasticBeanstalkEnvironmentHijackableResource) AnalyzeForHijack() (HijackChain, error) { - return HijackChain{[]HijackChainElement{}}, nil + switch eb.EventName { + case "CreateEnvironment": + return eb.analyzeCreatedEnvironment() + case "TerminateEnvironment": + return eb.analyzeTerminatedEnvironment() + default: + return HijackChain{}, errors.New("unknown elasticbeanstalk event to analyze for hijacks") + } +} + +func (eb *AWSElasticBeanstalkEnvironmentHijackableResource) analyzeCreatedEnvironment() (HijackChain, error) { + return HijackChain{}, nil +} + +func (eb *AWSElasticBeanstalkEnvironmentHijackableResource) analyzeTerminatedEnvironment() (HijackChain, error) { + resolver, err := graph.NewResolver() + if err != nil { + eb.GetLogger().Errorf("error creating a new resolver to evaluate elasticbeanstalk hijacks: %v", err.Error()) + return HijackChain{}, err + } + + ctx := context.Background() + chain, err := resolver.Query().GetElasticbeanstalkUpstreamHijack(ctx, []string{eb.CNAME, eb.Endpoint}) + if err != nil { + eb.GetLogger().Errorf("error querying graph for elasticbeanstalk hijack analysis: %v", err.Error()) + } + + hijacksChain := &HijackChain{Chain: make([]HijackChainElement, 0)} + + for _, link := range chain { + hijacksChain.Chain = append(hijacksChain.Chain, HijackChainElement{ + AccountId: link.Account, + Resource: link.ID, + ResourceType: link.Type.String(), + ResourceReferenced: link.Value.ValueID, + }) + } + + return *hijacksChain, nil } diff --git a/cloud-inquisitor/graph/generated/generated.go b/cloud-inquisitor/graph/generated/generated.go index 91b325a..9d25d9b 100644 --- a/cloud-inquisitor/graph/generated/generated.go +++ b/cloud-inquisitor/graph/generated/generated.go @@ -61,6 +61,15 @@ type ComplexityRoot struct { Origins func(childComplexity int) int } + ElasticbeanstalkEnvironment struct { + ApplicationName func(childComplexity int) int + CName func(childComplexity int) int + EnvironmentID func(childComplexity int) int + EnvironmentName func(childComplexity int) int + EnvironmentURL func(childComplexity int) int + Region func(childComplexity int) int + } + HijackableResource struct { Account func(childComplexity int) int ID func(childComplexity int) int @@ -90,6 +99,7 @@ type ComplexityRoot struct { Accounts func(childComplexity int) int Distribution func(childComplexity int, id string) int Distributions func(childComplexity int) int + ElasticbeanstalkEnvironments func(childComplexity int) int GetElasticbeanstalkUpstreamHijack func(childComplexity int, endpoints []string) int HijackChainByDomain func(childComplexity int, domain string, typeArg model.Type) int Origin func(childComplexity int, id string) int @@ -155,6 +165,7 @@ type QueryResolver interface { PointedAtByOrigin(ctx context.Context, domain string) ([]*model.Origin, error) OriginGroups(ctx context.Context) ([]*model.OriginGroup, error) PointedAtByOriginGroup(ctx context.Context, domain string) ([]*model.OriginGroup, error) + ElasticbeanstalkEnvironments(ctx context.Context) ([]*model.ElasticbeanstalkEnvironment, error) GetElasticbeanstalkUpstreamHijack(ctx context.Context, endpoints []string) ([]*model.HijackableResource, error) HijackChainByDomain(ctx context.Context, domain string, typeArg model.Type) (*model.HijackableResourceChain, error) } @@ -249,6 +260,48 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Distribution.Origins(childComplexity), true + case "ElasticbeanstalkEnvironment.applicationName": + if e.complexity.ElasticbeanstalkEnvironment.ApplicationName == nil { + break + } + + return e.complexity.ElasticbeanstalkEnvironment.ApplicationName(childComplexity), true + + case "ElasticbeanstalkEnvironment.cname": + if e.complexity.ElasticbeanstalkEnvironment.CName == nil { + break + } + + return e.complexity.ElasticbeanstalkEnvironment.CName(childComplexity), true + + case "ElasticbeanstalkEnvironment.environmentID": + if e.complexity.ElasticbeanstalkEnvironment.EnvironmentID == nil { + break + } + + return e.complexity.ElasticbeanstalkEnvironment.EnvironmentID(childComplexity), true + + case "ElasticbeanstalkEnvironment.environmentName": + if e.complexity.ElasticbeanstalkEnvironment.EnvironmentName == nil { + break + } + + return e.complexity.ElasticbeanstalkEnvironment.EnvironmentName(childComplexity), true + + case "ElasticbeanstalkEnvironment.environmentURL": + if e.complexity.ElasticbeanstalkEnvironment.EnvironmentURL == nil { + break + } + + return e.complexity.ElasticbeanstalkEnvironment.EnvironmentURL(childComplexity), true + + case "ElasticbeanstalkEnvironment.region": + if e.complexity.ElasticbeanstalkEnvironment.Region == nil { + break + } + + return e.complexity.ElasticbeanstalkEnvironment.Region(childComplexity), true + case "HijackableResource.account": if e.complexity.HijackableResource.Account == nil { break @@ -371,6 +424,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.Distributions(childComplexity), true + case "Query.elasticbeanstalkEnvironments": + if e.complexity.Query.ElasticbeanstalkEnvironments == nil { + break + } + + return e.complexity.Query.ElasticbeanstalkEnvironments(childComplexity), true + case "Query.getElasticbeanstalkUpstreamHijack": if e.complexity.Query.GetElasticbeanstalkUpstreamHijack == nil { break @@ -704,6 +764,15 @@ type OriginGroup { origins: [Value!]! } +type ElasticbeanstalkEnvironment { + environmentID: ID! + environmentName: String! + applicationName: String! + environmentURL: String! + cname: String! + region: String! +} + type HijackableResource { id: ID! account: String! @@ -744,6 +813,7 @@ type Query { originGroups: [OriginGroup!]! pointedAtByOriginGroup(domain: String!): [OriginGroup!]! + elasticbeanstalkEnvironments: [ElasticbeanstalkEnvironment!]! getElasticbeanstalkUpstreamHijack(endpoints: [String!]!): [HijackableResource!]! hijackChainByDomain(domain: String!, type: Type!): HijackableResourceChain! @@ -1323,6 +1393,210 @@ func (ec *executionContext) _Distribution_originGroups(ctx context.Context, fiel return ec.marshalNOriginGroup2ᚕᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐOriginGroupᚄ(ctx, field.Selections, res) } +func (ec *executionContext) _ElasticbeanstalkEnvironment_environmentID(ctx context.Context, field graphql.CollectedField, obj *model.ElasticbeanstalkEnvironment) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "ElasticbeanstalkEnvironment", + Field: field, + Args: nil, + IsMethod: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.EnvironmentID, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNID2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _ElasticbeanstalkEnvironment_environmentName(ctx context.Context, field graphql.CollectedField, obj *model.ElasticbeanstalkEnvironment) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "ElasticbeanstalkEnvironment", + Field: field, + Args: nil, + IsMethod: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.EnvironmentName, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _ElasticbeanstalkEnvironment_applicationName(ctx context.Context, field graphql.CollectedField, obj *model.ElasticbeanstalkEnvironment) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "ElasticbeanstalkEnvironment", + Field: field, + Args: nil, + IsMethod: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.ApplicationName, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _ElasticbeanstalkEnvironment_environmentURL(ctx context.Context, field graphql.CollectedField, obj *model.ElasticbeanstalkEnvironment) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "ElasticbeanstalkEnvironment", + Field: field, + Args: nil, + IsMethod: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.EnvironmentURL, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _ElasticbeanstalkEnvironment_cname(ctx context.Context, field graphql.CollectedField, obj *model.ElasticbeanstalkEnvironment) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "ElasticbeanstalkEnvironment", + Field: field, + Args: nil, + IsMethod: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.CName, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _ElasticbeanstalkEnvironment_region(ctx context.Context, field graphql.CollectedField, obj *model.ElasticbeanstalkEnvironment) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "ElasticbeanstalkEnvironment", + Field: field, + Args: nil, + IsMethod: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Region, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + func (ec *executionContext) _HijackableResource_id(ctx context.Context, field graphql.CollectedField, obj *model.HijackableResource) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -2376,6 +2650,40 @@ func (ec *executionContext) _Query_pointedAtByOriginGroup(ctx context.Context, f return ec.marshalNOriginGroup2ᚕᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐOriginGroupᚄ(ctx, field.Selections, res) } +func (ec *executionContext) _Query_elasticbeanstalkEnvironments(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().ElasticbeanstalkEnvironments(rctx) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]*model.ElasticbeanstalkEnvironment) + fc.Result = res + return ec.marshalNElasticbeanstalkEnvironment2ᚕᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐElasticbeanstalkEnvironmentᚄ(ctx, field.Selections, res) +} + func (ec *executionContext) _Query_getElasticbeanstalkUpstreamHijack(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -4080,6 +4388,58 @@ func (ec *executionContext) _Distribution(ctx context.Context, sel ast.Selection return out } +var elasticbeanstalkEnvironmentImplementors = []string{"ElasticbeanstalkEnvironment"} + +func (ec *executionContext) _ElasticbeanstalkEnvironment(ctx context.Context, sel ast.SelectionSet, obj *model.ElasticbeanstalkEnvironment) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, elasticbeanstalkEnvironmentImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("ElasticbeanstalkEnvironment") + case "environmentID": + out.Values[i] = ec._ElasticbeanstalkEnvironment_environmentID(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "environmentName": + out.Values[i] = ec._ElasticbeanstalkEnvironment_environmentName(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "applicationName": + out.Values[i] = ec._ElasticbeanstalkEnvironment_applicationName(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "environmentURL": + out.Values[i] = ec._ElasticbeanstalkEnvironment_environmentURL(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "cname": + out.Values[i] = ec._ElasticbeanstalkEnvironment_cname(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "region": + out.Values[i] = ec._ElasticbeanstalkEnvironment_region(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + var hijackableResourceImplementors = []string{"HijackableResource"} func (ec *executionContext) _HijackableResource(ctx context.Context, sel ast.SelectionSet, obj *model.HijackableResource) graphql.Marshaler { @@ -4478,6 +4838,20 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr } return res }) + case "elasticbeanstalkEnvironments": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_elasticbeanstalkEnvironments(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "getElasticbeanstalkUpstreamHijack": field := field out.Concurrently(i, func() (res graphql.Marshaler) { @@ -5025,6 +5399,57 @@ func (ec *executionContext) marshalNDistribution2ᚖgithubᚗcomᚋRiotGamesᚋc return ec._Distribution(ctx, sel, v) } +func (ec *executionContext) marshalNElasticbeanstalkEnvironment2githubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐElasticbeanstalkEnvironment(ctx context.Context, sel ast.SelectionSet, v model.ElasticbeanstalkEnvironment) graphql.Marshaler { + return ec._ElasticbeanstalkEnvironment(ctx, sel, &v) +} + +func (ec *executionContext) marshalNElasticbeanstalkEnvironment2ᚕᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐElasticbeanstalkEnvironmentᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.ElasticbeanstalkEnvironment) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNElasticbeanstalkEnvironment2ᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐElasticbeanstalkEnvironment(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + return ret +} + +func (ec *executionContext) marshalNElasticbeanstalkEnvironment2ᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐElasticbeanstalkEnvironment(ctx context.Context, sel ast.SelectionSet, v *model.ElasticbeanstalkEnvironment) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + return ec._ElasticbeanstalkEnvironment(ctx, sel, v) +} + func (ec *executionContext) marshalNHijackableResource2githubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐHijackableResource(ctx context.Context, sel ast.SelectionSet, v model.HijackableResource) graphql.Marshaler { return ec._HijackableResource(ctx, sel, &v) } diff --git a/cloud-inquisitor/graph/model/elasticbeanstalk_environment.go b/cloud-inquisitor/graph/model/elasticbeanstalk_environment.go new file mode 100644 index 0000000..62a5531 --- /dev/null +++ b/cloud-inquisitor/graph/model/elasticbeanstalk_environment.go @@ -0,0 +1,14 @@ +package model + +import "github.com/jinzhu/gorm" + +type ElasticbeanstalkEnvironment struct { + gorm.Model + AccountID uint + EnvironmentID string `json:"environmentID"` + ApplicationName string `json:"applicationName"` + EnvironmentName string `json:"environmentName"` + EnvironmentURL string `json:"environmentURL"` + CName string `json:"cname"` + Region string `json:"region"` +} diff --git a/cloud-inquisitor/graph/resolver.go b/cloud-inquisitor/graph/resolver.go index 6606946..b23a81a 100644 --- a/cloud-inquisitor/graph/resolver.go +++ b/cloud-inquisitor/graph/resolver.go @@ -102,6 +102,10 @@ func CreateTables() error { db.CreateTable(&model.OriginGroup{}) } + if !db.HasTable(&model.ElasticbeanstalkEnvironment{}) { + db.CreateTable(&model.ElasticbeanstalkEnvironment{}) + } + return nil } @@ -123,6 +127,7 @@ func DropTables() error { &model.Distribution{}, &model.Origin{}, &model.OriginGroup{}, + &model.ElasticbeanstalkEnvironment{}, ).Error if err != nil { @@ -150,6 +155,7 @@ func MigrateTables() error { &model.Distribution{}, &model.Origin{}, &model.OriginGroup{}, + &model.ElasticbeanstalkEnvironment{}, ).Error if err != nil { @@ -164,7 +170,7 @@ type Resolver struct { } func NewResolver() (*Resolver, error) { - db, err := graph.NewDBConnection() + db, err := NewDBConnection() if err != nil { db.Close() return nil, err diff --git a/cloud-inquisitor/graph/schema.graphqls b/cloud-inquisitor/graph/schema.graphqls index 684daa0..401d1f9 100644 --- a/cloud-inquisitor/graph/schema.graphqls +++ b/cloud-inquisitor/graph/schema.graphqls @@ -51,6 +51,15 @@ type OriginGroup { origins: [Value!]! } +type ElasticbeanstalkEnvironment { + environmentID: ID! + environmentName: String! + applicationName: String! + environmentURL: String! + cname: String! + region: String! +} + type HijackableResource { id: ID! account: String! @@ -91,6 +100,7 @@ type Query { originGroups: [OriginGroup!]! pointedAtByOriginGroup(domain: String!): [OriginGroup!]! + elasticbeanstalkEnvironments: [ElasticbeanstalkEnvironment!]! getElasticbeanstalkUpstreamHijack(endpoints: [String!]!): [HijackableResource!]! hijackChainByDomain(domain: String!, type: Type!): HijackableResourceChain! diff --git a/cloud-inquisitor/graph/schema.resolvers.go b/cloud-inquisitor/graph/schema.resolvers.go index d225a81..d63b96a 100644 --- a/cloud-inquisitor/graph/schema.resolvers.go +++ b/cloud-inquisitor/graph/schema.resolvers.go @@ -430,6 +430,24 @@ func (r *queryResolver) PointedAtByOriginGroup(ctx context.Context, domain strin return originGroups, nil } +func (r *queryResolver) ElasticbeanstalkEnvironments(ctx context.Context) ([]*model.ElasticbeanstalkEnvironment, error) { + log.Debug("getting all elasticbeanstalk environments") + var environments []*model.ElasticbeanstalkEnvironment + err := r.DB.Find(&environments).Error + if err != nil { + log.Errorf("error getting all elasticbeanstalk environments: %v", err.Error()) + return []*model.ElasticbeanstalkEnvironment{}, err + } + + if log.GetLevel() == log.DebugLevel { + for _, env := range environments { + log.Debugf("got elasticbeanstalk environment: %#v", *env) + } + } + + return environments, nil +} + func (r *queryResolver) GetElasticbeanstalkUpstreamHijack(ctx context.Context, endpoints []string) ([]*model.HijackableResource, error) { // elasticbeanstalks are endpoints are only fronted by other resources hijackChain := []*model.HijackableResource{} diff --git a/cloud-inquisitor/resource_hijackable.go b/cloud-inquisitor/resource_hijackable.go index 127a6eb..f8f1328 100644 --- a/cloud-inquisitor/resource_hijackable.go +++ b/cloud-inquisitor/resource_hijackable.go @@ -85,13 +85,13 @@ func NewHijackableResource(event events.CloudWatchEvent, ctx context.Context, me } if eventName, ok := detailMap["eventName"]; ok { switch eventName { - case "TerminateEnvironment": + case "TerminateEnvironment", "CreateEnvironment": resource = &AWSElasticBeanstalkEnvironmentHijackableResource{} resourceErr := resource.NewFromEventBus(event, ctx, metadata) return resource, resourceErr default: resource = &StubResource{} - return resource, errors.New("unknown route53 eventName") + return resource, errors.New("unknown elasticbeanstalk eventName") } } else { resource = &StubResource{} diff --git a/cloud-inquisitor/serverless/hijack_graph_analyzer/main.go b/cloud-inquisitor/serverless/hijack_graph_analyzer/main.go index 421c67a..108d43d 100644 --- a/cloud-inquisitor/serverless/hijack_graph_analyzer/main.go +++ b/cloud-inquisitor/serverless/hijack_graph_analyzer/main.go @@ -19,11 +19,6 @@ func handlerRequest(ctx context.Context, resource cloudinquisitor.PassableResour return resource, err } - err = parsedResource.RefreshState() - if err != nil { - return resource, err - } - hijackChain, err := parsedResource.AnalyzeForHijack() if err != nil { return cloudinquisitor.PassableResource{ @@ -47,7 +42,6 @@ func handlerRequest(ctx context.Context, resource cloudinquisitor.PassableResour // send notification } - err = parsedResource.PublishState() if err != nil { return cloudinquisitor.PassableResource{ Resource: parsedResource, diff --git a/cloud-inquisitor/serverless/hijack_graph_updater/main.go b/cloud-inquisitor/serverless/hijack_graph_updater/main.go index c2f82ec..bbc6121 100644 --- a/cloud-inquisitor/serverless/hijack_graph_updater/main.go +++ b/cloud-inquisitor/serverless/hijack_graph_updater/main.go @@ -3,7 +3,7 @@ package main import ( "context" - "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor" + cloudinquisitor "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor" "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/instrumentation/newrelic" "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/settings" ) @@ -17,11 +17,6 @@ func handlerRequest(ctx context.Context, resource cloudinquisitor.PassableResour return resource, err } - err = parsedResource.RefreshState() - if err != nil { - return resource, err - } - err = parsedResource.PublishState() if err != nil { return resource, err diff --git a/cloud-inquisitor/serverless/hijack_initializer/main.go b/cloud-inquisitor/serverless/hijack_initializer/main.go index 0df597f..ffe8c5c 100644 --- a/cloud-inquisitor/serverless/hijack_initializer/main.go +++ b/cloud-inquisitor/serverless/hijack_initializer/main.go @@ -26,6 +26,11 @@ func handlerRequest(ctx context.Context, event events.CloudWatchEvent) (passable return passableResourcesStruct{}, err } + err = resource.RefreshState() + if err != nil { + return passableResourcesStruct{}, err + } + if resource.GetType() == cloudinquisitor.SERVICE_STUB && settings.GetString("stub_resources") != "enabled" { return passableResourcesStruct{[]cloudinquisitor.PassableResource{ cloudinquisitor.PassableResource{ diff --git a/docs/Elasticbeanstalk.md b/docs/Elasticbeanstalk.md new file mode 100644 index 0000000..f7b3049 --- /dev/null +++ b/docs/Elasticbeanstalk.md @@ -0,0 +1,27 @@ +AWS Elastic Beanstalk +====================== + + +AWS Elastic Beanstalk is a service closer to a traditional PaaS such as Heroku. It alows a user to supply code and configurations and takes care of all of the infrastructure needed to deploy and make the application available. + +As such, Elastic Beanstalk is a sink when it comes to Cloud Inquisitor. It does not or will never front another service within AWS using strictly AWS service features. + +As of writing, the model used to represent the Elastic Beanstalk service caters only to the environment and not the applications as the application is merely a collection of environments with some grouping features overlaid. + +```golang +type ElasticbeanstalkEnvironment struct { + gorm.Model + AccountID uint + EnvironmentID string `json:"environmentID"` + ApplicationName string `json:"applicationName"` + EnvironmentName string `json:"environmentName"` + EnvironmentURL string `json:"environmentURL"` + CName string `json:"cname"` + Region string `json:"region" +} +``` + +This model includes all of the information needed to track and query Elastic Beanstalk enpoints in all AWS accounts. + + + diff --git a/go.mod b/go.mod index 815a68f..68796fa 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( golang.org/x/net v0.0.0-20200625001655-4c5254603344 // indirect golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 // indirect - golang.org/x/sys v0.0.0-20200908134130-d2e65c121b96 // indirect + golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 // indirect golang.org/x/text v0.3.3 // indirect google.golang.org/appengine v1.6.6 // indirect google.golang.org/genproto v0.0.0-20200626011028-ee7919e894b5 // indirect diff --git a/go.sum b/go.sum index 6f18ea9..61d8204 100644 --- a/go.sum +++ b/go.sum @@ -539,6 +539,8 @@ golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a h1:i47hUS795cOydZI4AwJQCKXOr golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200908134130-d2e65c121b96 h1:gJciq3lOg0eS9fSZJcoHfv7q1BfC6cJfnmSSKL1yu3Q= golang.org/x/sys v0.0.0-20200908134130-d2e65c121b96/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 h1:W0lCpv29Hv0UaM1LXb9QlBHLNP8UFfcKjblhVCWftOM= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= diff --git a/terraform_modules/event_rule/patterns/elasticbeanstalk_dns_hijacks.json b/terraform_modules/event_rule/patterns/elasticbeanstalk_dns_hijacks.json index 637baf2..66783d9 100644 --- a/terraform_modules/event_rule/patterns/elasticbeanstalk_dns_hijacks.json +++ b/terraform_modules/event_rule/patterns/elasticbeanstalk_dns_hijacks.json @@ -11,7 +11,8 @@ ], "eventName": [ "DeleteApplication", - "TerminateEnvironment" + "TerminateEnvironment", + "CreateEnvironment" ] } } \ No newline at end of file diff --git a/terraform_modules/step_function/step_function_definitions/domain_hijack.tpl b/terraform_modules/step_function/step_function_definitions/domain_hijack.tpl index 7156162..522f866 100644 --- a/terraform_modules/step_function/step_function_definitions/domain_hijack.tpl +++ b/terraform_modules/step_function/step_function_definitions/domain_hijack.tpl @@ -1,12 +1,7 @@ { "Comment": "Hijack workflow to track, alert, and remediate potential domain hijacks", - %{ if init != "" ~} "StartAt": "Init", - %{ else ~} - "StartAt": "Resource Has Been Remediated", - %{ endif ~} "States": { - %{ if init != "" ~} "Init": { "Type": "Task", "Resource": "${init}", @@ -18,97 +13,16 @@ "ItemsPath": "$.resources", "MaxConcurrency": 1, "Iterator": { - "StartAt": "Track or Analyze For Hijack", + "StartAt": "Analyze for Hijack", "States": { - "Track or Analyze For Hijack": { - "Type": "Choice", - "Choices": [ - { - "Or": [ - { - "Variable": "$.Resource.EventName", - "StringEquals": "CreateHostedZone" - }, - { - "And": [ - { - "Variable": "$.Resource.EventName", - "StringEquals": "ChangeResourceRecordSets" - }, - { - "Variable": "$.Resource.Action", - "StringEquals": "CREATE" - } - ] - }, - { - "Variable": "$.Resource.EventName", - "StringEquals": "CreateDistribution" - } - ], - "Next": "Update DNS Hijack Resource Graph" - }, - { - "Or": [ - { - "And": [ - { - "Variable": "$.Resource.EventName", - "StringEquals": "ChangeResourceRecordSets" - }, - { - "Variable": "$.Resource.Action", - "StringEquals": "UPSERT" - } - ] - }, - { - "Variable": "$.Resource.EventName", - "StringEquals": "UpdateDistribution" - } - ], - "Next": "Track and Analyze for Hijack" - }, - { - "Or":[ - { - "Variable": "$.Resource.EventName", - "StringEquals": "TerminateEnvironment" - } - ], - "Next": "Analyze for Hijack and Remove From Graph" - } - ], - "Default": "End of iterator" - }, - "Update DNS Hijack Resource Graph": { - "Type": "Task", - "Resource": "${graph_updater}", - "End": true - }, - "Analyze for Hijack and Remove From Graph": { + "Analyze for Hijack": { "Type": "Task", "Resource": "${graph_analyzer}", - "End": true - }, - "Track and Analyze for Hijack": { - "Type": "Parallel", - "End": true, - "Branches": [ - { - "StartAt": "Update DNS Hijack Resource Graph - Parallel", - "States": { - "Update DNS Hijack Resource Graph - Parallel": { - "Type": "Task", - "Resource": "${graph_updater}", - "End": true - } - } - } - ] + "Next": "Track Resource Changes" }, - "End of iterator" : { - "Type": "Pass", + "Track Resource Changes": { + "Type": "Task", + "Resource": "${graph_updater}", "End": true } } @@ -116,7 +30,6 @@ "ResultPath": "$.resources_completed", "Next": "Resource Has Been Remediated" }, - %{ endif } "Resource Has Been Remediated": { "Type": "Succeed" } From bbd8670deae1375f1b9729f5f5e65a2646139214 Mon Sep 17 00:00:00 2001 From: Jesse Taylor Date: Tue, 15 Sep 2020 09:43:11 -0700 Subject: [PATCH 6/9] finishing up template generation for notifcations. wires up notifiactions to analysis. includes analysis for elasticbeanstalks --- cloud-inquisitor/aws_cloudfront_resource.go | 4 +- .../aws_elasticbeanstalk_resource.go | 32 ++-- .../aws_route53_record_resource.go | 8 +- cloud-inquisitor/aws_route53_zone_resource.go | 4 +- cloud-inquisitor/graph/generated/generated.go | 138 +++++++++++++++--- cloud-inquisitor/graph/model/models_gen.go | 8 +- cloud-inquisitor/graph/schema.graphqls | 5 +- cloud-inquisitor/graph/schema.resolvers.go | 64 +++++++- cloud-inquisitor/notification/templates.go | 72 +++++++++ .../notification/templates_test.go | 36 +++++ cloud-inquisitor/resource_hijackable.go | 15 +- .../serverless/hijack_graph_analyzer/main.go | 50 ++++++- cloud-inquisitor/stub_resource.go | 5 +- go.mod | 2 +- go.sum | 2 + terraform_modules/project_role/iam.tf | 25 ++++ 16 files changed, 384 insertions(+), 86 deletions(-) diff --git a/cloud-inquisitor/aws_cloudfront_resource.go b/cloud-inquisitor/aws_cloudfront_resource.go index ea1bf0a..a8ee0ff 100644 --- a/cloud-inquisitor/aws_cloudfront_resource.go +++ b/cloud-inquisitor/aws_cloudfront_resource.go @@ -504,6 +504,6 @@ func (cf *AWSCloudFrontDistributionHijackableResource) PublishState() error { return nil } -func (cf *AWSCloudFrontDistributionHijackableResource) AnalyzeForHijack() (HijackChain, error) { - return HijackChain{[]HijackChainElement{}}, nil +func (cf *AWSCloudFrontDistributionHijackableResource) AnalyzeForHijack() (*model.HijackableResourceChain, error) { + return &model.HijackableResourceChain{}, nil } diff --git a/cloud-inquisitor/aws_elasticbeanstalk_resource.go b/cloud-inquisitor/aws_elasticbeanstalk_resource.go index 63f5e73..9231fb7 100644 --- a/cloud-inquisitor/aws_elasticbeanstalk_resource.go +++ b/cloud-inquisitor/aws_elasticbeanstalk_resource.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "errors" + "fmt" "reflect" "time" @@ -416,44 +417,37 @@ func (eb *AWSElasticBeanstalkEnvironmentHijackableResource) publishDeletedEnviro return nil } -func (eb *AWSElasticBeanstalkEnvironmentHijackableResource) AnalyzeForHijack() (HijackChain, error) { +func (eb *AWSElasticBeanstalkEnvironmentHijackableResource) AnalyzeForHijack() (*model.HijackableResourceChain, error) { switch eb.EventName { case "CreateEnvironment": return eb.analyzeCreatedEnvironment() case "TerminateEnvironment": return eb.analyzeTerminatedEnvironment() default: - return HijackChain{}, errors.New("unknown elasticbeanstalk event to analyze for hijacks") + return &model.HijackableResourceChain{}, errors.New("unknown elasticbeanstalk event to analyze for hijacks") } } -func (eb *AWSElasticBeanstalkEnvironmentHijackableResource) analyzeCreatedEnvironment() (HijackChain, error) { - return HijackChain{}, nil +func (eb *AWSElasticBeanstalkEnvironmentHijackableResource) analyzeCreatedEnvironment() (*model.HijackableResourceChain, error) { + /* + No Hijack is possible for creations as beanstalks are pure sinks + */ + return &model.HijackableResourceChain{}, nil } -func (eb *AWSElasticBeanstalkEnvironmentHijackableResource) analyzeTerminatedEnvironment() (HijackChain, error) { +func (eb *AWSElasticBeanstalkEnvironmentHijackableResource) analyzeTerminatedEnvironment() (*model.HijackableResourceChain, error) { resolver, err := graph.NewResolver() if err != nil { eb.GetLogger().Errorf("error creating a new resolver to evaluate elasticbeanstalk hijacks: %v", err.Error()) - return HijackChain{}, err + return &model.HijackableResourceChain{}, err + } ctx := context.Background() - chain, err := resolver.Query().GetElasticbeanstalkUpstreamHijack(ctx, []string{eb.CNAME, eb.Endpoint}) + chain, err := resolver.Query().HijackChainByDomain(ctx, fmt.Sprintf("elasticbeanstalk-%s-%s-%s-%s", eb.ApplicationName, eb.Region, eb.EnvironmentName, eb.EnvironmentId), []string{eb.CNAME, eb.Endpoint}, model.TypeElasticbeanstalk) if err != nil { eb.GetLogger().Errorf("error querying graph for elasticbeanstalk hijack analysis: %v", err.Error()) } - hijacksChain := &HijackChain{Chain: make([]HijackChainElement, 0)} - - for _, link := range chain { - hijacksChain.Chain = append(hijacksChain.Chain, HijackChainElement{ - AccountId: link.Account, - Resource: link.ID, - ResourceType: link.Type.String(), - ResourceReferenced: link.Value.ValueID, - }) - } - - return *hijacksChain, nil + return chain, err } diff --git a/cloud-inquisitor/aws_route53_record_resource.go b/cloud-inquisitor/aws_route53_record_resource.go index d7e108d..37cb353 100644 --- a/cloud-inquisitor/aws_route53_record_resource.go +++ b/cloud-inquisitor/aws_route53_record_resource.go @@ -285,8 +285,8 @@ func (r *AWSRoute53RecordSet) isPending() (bool, error) { return true, nil } -func (r *AWSRoute53RecordSet) AnalyzeForHijack() (HijackChain, error) { - return HijackChain{[]HijackChainElement{}}, nil +func (r *AWSRoute53RecordSet) AnalyzeForHijack() (*model.HijackableResourceChain, error) { + return &model.HijackableResourceChain{}, nil } func (r *AWSRoute53Record) NewFromEventBus(_ events.CloudWatchEvent, _ context.Context, _ map[string]interface{}) error { @@ -537,6 +537,6 @@ func (r *AWSRoute53Record) createRecordEntries() error { return nil } -func (r *AWSRoute53Record) AnalyzeForHijack() (HijackChain, error) { - return HijackChain{[]HijackChainElement{}}, nil +func (r *AWSRoute53Record) AnalyzeForHijack() (*model.HijackableResourceChain, error) { + return &model.HijackableResourceChain{}, nil } diff --git a/cloud-inquisitor/aws_route53_zone_resource.go b/cloud-inquisitor/aws_route53_zone_resource.go index ae7b49a..3e87851 100644 --- a/cloud-inquisitor/aws_route53_zone_resource.go +++ b/cloud-inquisitor/aws_route53_zone_resource.go @@ -337,6 +337,6 @@ func (r *AWSRoute53Zone) isPending() (bool, error) { return true, nil } -func (r *AWSRoute53Zone) AnalyzeForHijack() (HijackChain, error) { - return HijackChain{[]HijackChainElement{}}, nil +func (r *AWSRoute53Zone) AnalyzeForHijack() (*model.HijackableResourceChain, error) { + return &model.HijackableResourceChain{}, nil } diff --git a/cloud-inquisitor/graph/generated/generated.go b/cloud-inquisitor/graph/generated/generated.go index 9d25d9b..ff38a28 100644 --- a/cloud-inquisitor/graph/generated/generated.go +++ b/cloud-inquisitor/graph/generated/generated.go @@ -78,10 +78,10 @@ type ComplexityRoot struct { } HijackableResourceChain struct { - Downsteam func(childComplexity int) int - ID func(childComplexity int) int - Resource func(childComplexity int) int - Upstream func(childComplexity int) int + Downstream func(childComplexity int) int + ID func(childComplexity int) int + Resource func(childComplexity int) int + Upstream func(childComplexity int) int } Origin struct { @@ -99,9 +99,10 @@ type ComplexityRoot struct { Accounts func(childComplexity int) int Distribution func(childComplexity int, id string) int Distributions func(childComplexity int) int + ElasticbeanstalkByEndpoint func(childComplexity int, endpoint string) int ElasticbeanstalkEnvironments func(childComplexity int) int GetElasticbeanstalkUpstreamHijack func(childComplexity int, endpoints []string) int - HijackChainByDomain func(childComplexity int, domain string, typeArg model.Type) int + HijackChainByDomain func(childComplexity int, id string, domains []string, typeArg model.Type) int Origin func(childComplexity int, id string) int OriginGroups func(childComplexity int) int Origins func(childComplexity int) int @@ -166,8 +167,9 @@ type QueryResolver interface { OriginGroups(ctx context.Context) ([]*model.OriginGroup, error) PointedAtByOriginGroup(ctx context.Context, domain string) ([]*model.OriginGroup, error) ElasticbeanstalkEnvironments(ctx context.Context) ([]*model.ElasticbeanstalkEnvironment, error) + ElasticbeanstalkByEndpoint(ctx context.Context, endpoint string) (*model.ElasticbeanstalkEnvironment, error) GetElasticbeanstalkUpstreamHijack(ctx context.Context, endpoints []string) ([]*model.HijackableResource, error) - HijackChainByDomain(ctx context.Context, domain string, typeArg model.Type) (*model.HijackableResourceChain, error) + HijackChainByDomain(ctx context.Context, id string, domains []string, typeArg model.Type) (*model.HijackableResourceChain, error) } type RecordResolver interface { Values(ctx context.Context, obj *model.Record) ([]*model.Value, error) @@ -330,12 +332,12 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.HijackableResource.Value(childComplexity), true - case "HijackableResourceChain.downsteam": - if e.complexity.HijackableResourceChain.Downsteam == nil { + case "HijackableResourceChain.downstream": + if e.complexity.HijackableResourceChain.Downstream == nil { break } - return e.complexity.HijackableResourceChain.Downsteam(childComplexity), true + return e.complexity.HijackableResourceChain.Downstream(childComplexity), true case "HijackableResourceChain.id": if e.complexity.HijackableResourceChain.ID == nil { @@ -424,6 +426,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.Distributions(childComplexity), true + case "Query.elasticbeanstalkByEndpoint": + if e.complexity.Query.ElasticbeanstalkByEndpoint == nil { + break + } + + args, err := ec.field_Query_elasticbeanstalkByEndpoint_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Query.ElasticbeanstalkByEndpoint(childComplexity, args["endpoint"].(string)), true + case "Query.elasticbeanstalkEnvironments": if e.complexity.Query.ElasticbeanstalkEnvironments == nil { break @@ -453,7 +467,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return 0, false } - return e.complexity.Query.HijackChainByDomain(childComplexity, args["domain"].(string), args["type"].(model.Type)), true + return e.complexity.Query.HijackChainByDomain(childComplexity, args["id"].(string), args["domains"].([]string), args["type"].(model.Type)), true case "Query.origin": if e.complexity.Query.Origin == nil { @@ -784,7 +798,7 @@ type HijackableResourceChain { id: ID! resource: HijackableResource! upstream: [HijackableResource!]! - downsteam: [HijackableResource!]! + downstream: [HijackableResource!]! } type Query { @@ -814,9 +828,10 @@ type Query { pointedAtByOriginGroup(domain: String!): [OriginGroup!]! elasticbeanstalkEnvironments: [ElasticbeanstalkEnvironment!]! + elasticbeanstalkByEndpoint(endpoint: String!): ElasticbeanstalkEnvironment! getElasticbeanstalkUpstreamHijack(endpoints: [String!]!): [HijackableResource!]! - hijackChainByDomain(domain: String!, type: Type!): HijackableResourceChain! + hijackChainByDomain(id: ID!, domains: [String!]!, type: Type!): HijackableResourceChain! }`, BuiltIn: false}, } @@ -882,6 +897,20 @@ func (ec *executionContext) field_Query_distribution_args(ctx context.Context, r return args, nil } +func (ec *executionContext) field_Query_elasticbeanstalkByEndpoint_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 string + if tmp, ok := rawArgs["endpoint"]; ok { + arg0, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["endpoint"] = arg0 + return args, nil +} + func (ec *executionContext) field_Query_getElasticbeanstalkUpstreamHijack_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -900,21 +929,29 @@ func (ec *executionContext) field_Query_hijackChainByDomain_args(ctx context.Con var err error args := map[string]interface{}{} var arg0 string - if tmp, ok := rawArgs["domain"]; ok { - arg0, err = ec.unmarshalNString2string(ctx, tmp) + if tmp, ok := rawArgs["id"]; ok { + arg0, err = ec.unmarshalNID2string(ctx, tmp) if err != nil { return nil, err } } - args["domain"] = arg0 - var arg1 model.Type + args["id"] = arg0 + var arg1 []string + if tmp, ok := rawArgs["domains"]; ok { + arg1, err = ec.unmarshalNString2ᚕstringᚄ(ctx, tmp) + if err != nil { + return nil, err + } + } + args["domains"] = arg1 + var arg2 model.Type if tmp, ok := rawArgs["type"]; ok { - arg1, err = ec.unmarshalNType2githubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐType(ctx, tmp) + arg2, err = ec.unmarshalNType2githubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐType(ctx, tmp) if err != nil { return nil, err } } - args["type"] = arg1 + args["type"] = arg2 return args, nil } @@ -1835,7 +1872,7 @@ func (ec *executionContext) _HijackableResourceChain_upstream(ctx context.Contex return ec.marshalNHijackableResource2ᚕᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐHijackableResourceᚄ(ctx, field.Selections, res) } -func (ec *executionContext) _HijackableResourceChain_downsteam(ctx context.Context, field graphql.CollectedField, obj *model.HijackableResourceChain) (ret graphql.Marshaler) { +func (ec *executionContext) _HijackableResourceChain_downstream(ctx context.Context, field graphql.CollectedField, obj *model.HijackableResourceChain) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -1852,7 +1889,7 @@ func (ec *executionContext) _HijackableResourceChain_downsteam(ctx context.Conte ctx = graphql.WithFieldContext(ctx, fc) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Downsteam, nil + return obj.Downstream, nil }) if err != nil { ec.Error(ctx, err) @@ -2684,6 +2721,47 @@ func (ec *executionContext) _Query_elasticbeanstalkEnvironments(ctx context.Cont return ec.marshalNElasticbeanstalkEnvironment2ᚕᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐElasticbeanstalkEnvironmentᚄ(ctx, field.Selections, res) } +func (ec *executionContext) _Query_elasticbeanstalkByEndpoint(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Query_elasticbeanstalkByEndpoint_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().ElasticbeanstalkByEndpoint(rctx, args["endpoint"].(string)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*model.ElasticbeanstalkEnvironment) + fc.Result = res + return ec.marshalNElasticbeanstalkEnvironment2ᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐElasticbeanstalkEnvironment(ctx, field.Selections, res) +} + func (ec *executionContext) _Query_getElasticbeanstalkUpstreamHijack(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -2749,7 +2827,7 @@ func (ec *executionContext) _Query_hijackChainByDomain(ctx context.Context, fiel fc.Args = args resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().HijackChainByDomain(rctx, args["domain"].(string), args["type"].(model.Type)) + return ec.resolvers.Query().HijackChainByDomain(rctx, args["id"].(string), args["domains"].([]string), args["type"].(model.Type)) }) if err != nil { ec.Error(ctx, err) @@ -4508,8 +4586,8 @@ func (ec *executionContext) _HijackableResourceChain(ctx context.Context, sel as if out.Values[i] == graphql.Null { invalids++ } - case "downsteam": - out.Values[i] = ec._HijackableResourceChain_downsteam(ctx, field, obj) + case "downstream": + out.Values[i] = ec._HijackableResourceChain_downstream(ctx, field, obj) if out.Values[i] == graphql.Null { invalids++ } @@ -4852,6 +4930,20 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr } return res }) + case "elasticbeanstalkByEndpoint": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_elasticbeanstalkByEndpoint(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "getElasticbeanstalkUpstreamHijack": field := field out.Concurrently(i, func() (res graphql.Marshaler) { diff --git a/cloud-inquisitor/graph/model/models_gen.go b/cloud-inquisitor/graph/model/models_gen.go index 741f318..3b210b4 100644 --- a/cloud-inquisitor/graph/model/models_gen.go +++ b/cloud-inquisitor/graph/model/models_gen.go @@ -16,10 +16,10 @@ type HijackableResource struct { } type HijackableResourceChain struct { - ID string `json:"id"` - Resource *HijackableResource `json:"resource"` - Upstream []*HijackableResource `json:"upstream"` - Downsteam []*HijackableResource `json:"downsteam"` + ID string `json:"id"` + Resource *HijackableResource `json:"resource"` + Upstream []*HijackableResource `json:"upstream"` + Downstream []*HijackableResource `json:"downstream"` } type Type string diff --git a/cloud-inquisitor/graph/schema.graphqls b/cloud-inquisitor/graph/schema.graphqls index 401d1f9..74ba5c8 100644 --- a/cloud-inquisitor/graph/schema.graphqls +++ b/cloud-inquisitor/graph/schema.graphqls @@ -71,7 +71,7 @@ type HijackableResourceChain { id: ID! resource: HijackableResource! upstream: [HijackableResource!]! - downsteam: [HijackableResource!]! + downstream: [HijackableResource!]! } type Query { @@ -101,8 +101,9 @@ type Query { pointedAtByOriginGroup(domain: String!): [OriginGroup!]! elasticbeanstalkEnvironments: [ElasticbeanstalkEnvironment!]! + elasticbeanstalkByEndpoint(endpoint: String!): ElasticbeanstalkEnvironment! getElasticbeanstalkUpstreamHijack(endpoints: [String!]!): [HijackableResource!]! - hijackChainByDomain(domain: String!, type: Type!): HijackableResourceChain! + hijackChainByDomain(id: ID!, domains: [String!]!, type: Type!): HijackableResourceChain! } \ No newline at end of file diff --git a/cloud-inquisitor/graph/schema.resolvers.go b/cloud-inquisitor/graph/schema.resolvers.go index d63b96a..345c0d0 100644 --- a/cloud-inquisitor/graph/schema.resolvers.go +++ b/cloud-inquisitor/graph/schema.resolvers.go @@ -448,6 +448,24 @@ func (r *queryResolver) ElasticbeanstalkEnvironments(ctx context.Context) ([]*mo return environments, nil } +func (r *queryResolver) ElasticbeanstalkByEndpoint(ctx context.Context, endpoint string) (*model.ElasticbeanstalkEnvironment, error) { + log.Debugf("getting elasticbeanstalk by domain: %v", endpoint) + var beanstalk model.ElasticbeanstalkEnvironment + err := r.DB.Find(&beanstalk, model.ElasticbeanstalkEnvironment{ + CName: endpoint, + }).Error + + if err == nil { + return &beanstalk, err + } + + err = r.DB.Find(&beanstalk, model.ElasticbeanstalkEnvironment{ + EnvironmentURL: endpoint, + }).Error + + return &beanstalk, err +} + func (r *queryResolver) GetElasticbeanstalkUpstreamHijack(ctx context.Context, endpoints []string) ([]*model.HijackableResource, error) { // elasticbeanstalks are endpoints are only fronted by other resources hijackChain := []*model.HijackableResource{} @@ -529,22 +547,56 @@ func (r *queryResolver) GetElasticbeanstalkUpstreamHijack(ctx context.Context, e } } + if log.GetLevel() == log.DebugLevel { + log.Debugf("found hijack chain for domains: %#v", endpoints) + for idx, chainElement := range hijackChain { + log.Debugf("%v: %#v", idx, *chainElement) + } + } + return hijackChain, nil } -func (r *queryResolver) HijackChainByDomain(ctx context.Context, domain string, typeArg model.Type) (*model.HijackableResourceChain, error) { +func (r *queryResolver) HijackChainByDomain(ctx context.Context, id string, domains []string, typeArg model.Type) (*model.HijackableResourceChain, error) { switch typeArg { case model.TypeElasticbeanstalk: - chain, err := r.Query().GetElasticbeanstalkUpstreamHijack(ctx, []string{domain}) + /* + ID string `json:"id"` + Account string `json:"account"` + Type Type `json:"type"` + Value *Value `json:"value"` + */ + beanstalk, err := r.Query().ElasticbeanstalkByEndpoint(ctx, domains[0]) + if err != nil { + log.Errorf("error getting beanstalk: %v", err.Error()) + return nil, err + } + + var account *model.Account = nil + err = r.DB.Find(account, beanstalk.AccountID).Error + if err != nil { + log.Errorf("unable to find account for beanstalk: %#v", *beanstalk) + return nil, err + } + + chain, err := r.Query().GetElasticbeanstalkUpstreamHijack(ctx, domains) if err != nil { log.Errorf("recieved error when querying for elastic beanstalk hijacks: %v", err.Error()) + return nil, err } return &model.HijackableResourceChain{ - ID: domain, - Resource: &model.HijackableResource{}, - Upstream: chain, - Downsteam: []*model.HijackableResource{}, + ID: id, + Resource: &model.HijackableResource{ + ID: id, + Account: account.AccountID, + Type: model.TypeElasticbeanstalk, + Value: &model.Value{ + ValueID: beanstalk.CName, + }, + }, + Upstream: chain, + Downstream: []*model.HijackableResource{}, }, err } diff --git a/cloud-inquisitor/notification/templates.go b/cloud-inquisitor/notification/templates.go index ab9768d..9279038 100644 --- a/cloud-inquisitor/notification/templates.go +++ b/cloud-inquisitor/notification/templates.go @@ -4,6 +4,7 @@ import ( "bytes" "html/template" + "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/graph/model" "github.com/gobuffalo/packr" ) @@ -28,6 +29,77 @@ type HijackNotificationContent struct { HijackChain []HijackChainElement } +func GenerateContent(rawChain *model.HijackableResourceChain) HijackNotificationContent { + content := HijackNotificationContent{ + PrimaryResource: rawChain.Resource.ID, + PrimaryAccountId: rawChain.Resource.Account, + PrimaryResourceType: rawChain.Resource.Type.String(), + } + + chain := []HijackChainElement{} + for idx, resource := range rawChain.Upstream { + if idx == len(rawChain.Upstream)-1 { + chain = append(chain, HijackChainElement{ + AccountId: resource.Account, + Resource: resource.ID, + ResourceType: resource.Type.String(), + ResourceReferenced: rawChain.Resource.ID, + ResourceReferencedType: rawChain.Resource.Type.String(), + }) + } else { + chain = append(chain, HijackChainElement{ + AccountId: resource.Account, + Resource: resource.ID, + ResourceType: resource.Type.String(), + ResourceReferenced: rawChain.Upstream[idx+1].ID, + ResourceReferencedType: rawChain.Upstream[idx+1].Type.String(), + }) + } + } + + if len(rawChain.Downstream) == 0 { + chain = append(chain, HijackChainElement{ + AccountId: rawChain.Resource.Account, + Resource: rawChain.Resource.ID, + ResourceType: rawChain.Resource.Type.String(), + ResourceReferenced: "not applicable", + ResourceReferencedType: "not applicable", + }) + } else { + chain = append(chain, HijackChainElement{ + AccountId: rawChain.Resource.Account, + Resource: rawChain.Resource.ID, + ResourceType: rawChain.Resource.Type.String(), + ResourceReferenced: rawChain.Downstream[0].ID, + ResourceReferencedType: rawChain.Downstream[0].Type.String(), + }) + } + + for idx, resource := range rawChain.Downstream { + if idx == len(rawChain.Downstream)-1 { + chain = append(chain, HijackChainElement{ + AccountId: resource.Account, + Resource: resource.ID, + ResourceType: resource.Type.String(), + ResourceReferenced: "not applicable", + ResourceReferencedType: "not applicable", + }) + } else { + chain = append(chain, HijackChainElement{ + AccountId: resource.Account, + Resource: resource.ID, + ResourceType: resource.Type.String(), + ResourceReferenced: rawChain.Downstream[idx+1].ID, + ResourceReferencedType: rawChain.Downstream[idx+1].Type.String(), + }) + } + } + + content.HijackChain = chain + + return content +} + func NewHijackHTML(content HijackNotificationContent) (string, error) { funcMap := map[string]interface{}{ "isEven": func(val int) bool { diff --git a/cloud-inquisitor/notification/templates_test.go b/cloud-inquisitor/notification/templates_test.go index 850aba5..8e8941a 100644 --- a/cloud-inquisitor/notification/templates_test.go +++ b/cloud-inquisitor/notification/templates_test.go @@ -4,6 +4,7 @@ import ( //"io/ioutil" "testing" + "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/graph/model" "github.com/gobuffalo/packr" ) @@ -164,3 +165,38 @@ func TestHijackTextWithChain(t *testing.T) { t.Fatal("generated and test text do not match") } } + +/* +AccountId: rawChain.Resource.Account, + Resource: rawChain.Resource.ID, + ResourceType: rawChain.Resource.Type.String(), + ResourceReferenced: "", + ResourceReferencedType: "", +*/ +func TestGenerateContent(t *testing.T) { + chain := &model.HijackableResourceChain{ + ID: "testID", + Resource: &model.HijackableResource{ + ID: "test root resource", + Account: "test root account", + Type: model.TypeElasticbeanstalk, + }, + Upstream: []*model.HijackableResource{ + &model.HijackableResource{ + ID: "test upstream ", + Account: "test upstream account", + Type: model.TypeElasticbeanstalk, + }, + }, + Downstream: []*model.HijackableResource{ + &model.HijackableResource{ + ID: "test downstream", + Account: "test downstream account", + Type: model.TypeElasticbeanstalk, + }, + }, + } + + content := GenerateContent(chain) + t.Logf("content struct %#v", content) +} diff --git a/cloud-inquisitor/resource_hijackable.go b/cloud-inquisitor/resource_hijackable.go index f8f1328..61f1379 100644 --- a/cloud-inquisitor/resource_hijackable.go +++ b/cloud-inquisitor/resource_hijackable.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" + "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/graph/model" "github.com/aws/aws-lambda-go/events" //"github.com/sirupsen/logrus" ) @@ -13,7 +14,7 @@ type HijackableResource interface { Resource NewFromEventBus(events.CloudWatchEvent, context.Context, map[string]interface{}) error NewFromPassableResource(PassableResource, context.Context, map[string]interface{}) error - AnalyzeForHijack() (HijackChain, error) + AnalyzeForHijack() (*model.HijackableResourceChain, error) // PublishState is provided to give an easy hook to // send and store struct state in a backend data store PublishState() error @@ -105,15 +106,3 @@ func NewHijackableResource(event events.CloudWatchEvent, ctx context.Context, me } return resource, nil } - -type HijackChain struct { - Chain []HijackChainElement -} - -type HijackChainElement struct { - AccountId string - Resource string - ResourceType string - ResourceReferenced string - ResourceReferencedType string -} diff --git a/cloud-inquisitor/serverless/hijack_graph_analyzer/main.go b/cloud-inquisitor/serverless/hijack_graph_analyzer/main.go index 108d43d..1d58ad5 100644 --- a/cloud-inquisitor/serverless/hijack_graph_analyzer/main.go +++ b/cloud-inquisitor/serverless/hijack_graph_analyzer/main.go @@ -3,11 +3,13 @@ package main import ( "context" "errors" - "reflect" + "fmt" cloudinquisitor "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor" "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/instrumentation/newrelic" + "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/notification" "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/settings" + "github.com/sirupsen/logrus" ) func handlerRequest(ctx context.Context, resource cloudinquisitor.PassableResource) (cloudinquisitor.PassableResource, error) { @@ -29,17 +31,49 @@ func handlerRequest(ctx context.Context, resource cloudinquisitor.PassableResour }, err } - if reflect.DeepEqual((cloudinquisitor.HijackChain{}), hijackChain) { + logger := parsedResource.GetLogger() + if logger.L.GetLevel() == logrus.DebugLevel { + logger.Debugf("hijack chain for resource with metadata: %#v", parsedResource.GetMetadata()) + logger.Debugf("root hijack element: %#v", *hijackChain.Resource) + for idx, hijackElement := range hijackChain.Upstream { + logger.Debugf("upstream hijack element %v: %#v", idx, *hijackElement) + } + for idx, hijackElement := range hijackChain.Downstream { + logger.Debugf("downstream hijack element %v: %#v", idx, *hijackElement) + } + } + + if len(hijackChain.Upstream) > 0 || len(hijackChain.Downstream) > 0 { + // send notification + content := notification.GenerateContent(hijackChain) + sendTo := settings.GetString("simple_email_service.verified_email") + msg, err := notification.NewHijackNotficationMessage("Potential DNS Hijack", []string{sendTo}, []string{sendTo}, content) + if err != nil { + return cloudinquisitor.PassableResource{ + Resource: parsedResource, + Type: parsedResource.GetType(), + Metadata: parsedResource.GetLogger().GetMetadata(), + Finished: true, + }, fmt.Errorf("error generating hijack message: %s", err.Error()) + } + + notifier, err := notification.NewNotifier() + if err != nil { + return cloudinquisitor.PassableResource{ + Resource: parsedResource, + Type: parsedResource.GetType(), + Metadata: parsedResource.GetLogger().GetMetadata(), + Finished: true, + }, errors.New("unable to get notifier to notify of potential hijack") + } + + _, err = notifier.SendNotification(msg) return cloudinquisitor.PassableResource{ Resource: parsedResource, Type: parsedResource.GetType(), Metadata: parsedResource.GetLogger().GetMetadata(), Finished: true, - }, errors.New("hijack analysis returned nil hijack chain") - } - - if len(hijackChain.Chain) > 0 { - // send notification + }, err } if err != nil { @@ -55,7 +89,7 @@ func handlerRequest(ctx context.Context, resource cloudinquisitor.PassableResour Resource: parsedResource, Type: parsedResource.GetType(), Metadata: parsedResource.GetLogger().GetMetadata(), - Finished: true, + Finished: false, }, nil } diff --git a/cloud-inquisitor/stub_resource.go b/cloud-inquisitor/stub_resource.go index 573fb03..13811e2 100644 --- a/cloud-inquisitor/stub_resource.go +++ b/cloud-inquisitor/stub_resource.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" + "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/graph/model" instrument "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/instrumentation" log "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/logger" "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/settings" @@ -115,6 +116,6 @@ func (t *StubResource) GetTags() map[string]string { return map[string]string{} } -func (t *StubResource) AnalyzeForHijack() (HijackChain, error) { - return HijackChain{[]HijackChainElement{}}, nil +func (t *StubResource) AnalyzeForHijack() (*model.HijackableResourceChain, error) { + return &model.HijackableResourceChain{}, nil } diff --git a/go.mod b/go.mod index 68796fa..b3bcc72 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( golang.org/x/net v0.0.0-20200625001655-4c5254603344 // indirect golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 // indirect - golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 // indirect + golang.org/x/sys v0.0.0-20200915084602-288bc346aa39 // indirect golang.org/x/text v0.3.3 // indirect google.golang.org/appengine v1.6.6 // indirect google.golang.org/genproto v0.0.0-20200626011028-ee7919e894b5 // indirect diff --git a/go.sum b/go.sum index 61d8204..2d72296 100644 --- a/go.sum +++ b/go.sum @@ -541,6 +541,8 @@ golang.org/x/sys v0.0.0-20200908134130-d2e65c121b96 h1:gJciq3lOg0eS9fSZJcoHfv7q1 golang.org/x/sys v0.0.0-20200908134130-d2e65c121b96/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 h1:W0lCpv29Hv0UaM1LXb9QlBHLNP8UFfcKjblhVCWftOM= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200915084602-288bc346aa39 h1:356XA7ITklAU2//sYkjFeco+dH1bCRD8XCJ9FIEsvo4= +golang.org/x/sys v0.0.0-20200915084602-288bc346aa39/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= diff --git a/terraform_modules/project_role/iam.tf b/terraform_modules/project_role/iam.tf index 9c21e6b..5df839a 100644 --- a/terraform_modules/project_role/iam.tf +++ b/terraform_modules/project_role/iam.tf @@ -114,6 +114,31 @@ resource "aws_iam_role_policy_attachment" "dns_hijack_permissions" { policy_arn = aws_iam_policy.dns_hijack_policy.arn } +data "aws_iam_policy_document" "ses_permissions" { + statement { + sid = "SESPermissions" + + actions = [ + "ses:SendEmail", + ] + + resources = [ + "*", + ] + } +} + +resource "aws_iam_policy" "ses_policy" { + name = "${var.environment}-${var.name}-ses_policy" + path = "/" + policy = data.aws_iam_policy_document.ses_permissions.json +} + +resource "aws_iam_role_policy_attachment" "ses_permissions" { + role = aws_iam_role.project_role.name + policy_arn = aws_iam_policy.ses_policy.arn +} + data "aws_iam_policy_document" "assume_role_permissions" { statement { sid = "AssumeRolePermissions" From 28409c87420b83d19636dcdb60929793c7848154 Mon Sep 17 00:00:00 2001 From: Jesse Taylor Date: Tue, 15 Sep 2020 13:54:05 -0700 Subject: [PATCH 7/9] updated readme --- README.md | 73 +++++++++++++++++++++++-------------------------------- 1 file changed, 30 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 3265388..e0f2305 100644 --- a/README.md +++ b/README.md @@ -40,58 +40,45 @@ Current supported remediation actions are: Example usage of terraform module: ```hcl -locals { - ec2_rule = < Date: Thu, 17 Sep 2020 15:56:49 -0700 Subject: [PATCH 8/9] finished wiring up s3 resource --- cloud-inquisitor/aws_s3_resource.go | 344 ++++++++++--- cloud-inquisitor/graph/generated/generated.go | 452 +++++++++++++++++- cloud-inquisitor/graph/model/models_gen.go | 4 +- cloud-inquisitor/graph/model/s3.go | 13 + cloud-inquisitor/graph/schema.graphqls | 12 +- cloud-inquisitor/graph/schema.resolvers.go | 124 +++++ cloud-inquisitor/resource.go | 5 +- cloud-inquisitor/resource_hijackable.go | 24 + go.mod | 2 +- go.sum | 4 + terraform_modules/event_rule/event_rule.tf | 1 + .../event_rule/patterns/s3_dns_hijack.json | 17 + 12 files changed, 920 insertions(+), 82 deletions(-) create mode 100644 cloud-inquisitor/graph/model/s3.go create mode 100644 terraform_modules/event_rule/patterns/s3_dns_hijack.json diff --git a/cloud-inquisitor/aws_s3_resource.go b/cloud-inquisitor/aws_s3_resource.go index 6e3543e..f9b86cd 100644 --- a/cloud-inquisitor/aws_s3_resource.go +++ b/cloud-inquisitor/aws_s3_resource.go @@ -7,7 +7,9 @@ import ( "fmt" "strings" - "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/instrumentation" + "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/graph" + "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/graph/model" + instrument "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/instrumentation" log "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/logger" "github.com/RiotGames/cloud-inquisitor/cloud-inquisitor/settings" openapis3 "github.com/RiotGames/cloud-inquisitor/ext/aws/s3" @@ -43,12 +45,65 @@ type AWSS3Storage struct { AccountID string Region string BucketName string + EventName string logger *log.Logger Tags map[string]string } -func (s *AWSS3Storage) NewFromEventBus(event events.CloudWatchEvent, ctx context.Context, passsedInMetadata map[string]interface{}) error { +/*func (s *AWSS3Storage) RefreshState() error { + var AWSInputSession = session.Must(session.NewSession()) + assumedSession, err := AWSAssumeRole(s.AccountID, "ROLE_NAME", AWSInputSession) + if err != nil { + return err + } + + s3Client := s3.New(assumedSession) + + taggingInput := &s3.GetBucketTaggingInput{ + Bucket: aws.String(s.BucketName), + } + + tagsResult, err := s3Client.GetBucketTagging(taggingInput) + if err != nil { + return nil + } + + s.Tags = map[string]string{} + for _, tag := range tagsResult.TagSet { + s.Tags[*tag.Key] = *tag.Value + } + return nil +}*/ + +func (s *AWSS3Storage) SendNotification() error { + s.logger.WithFields(logrus.Fields{"cloud-inquisitor-resource": "aws-s3"}).Debugf("SendNotifcation: %#v", *s) + return nil +} + +func (s3 *AWSS3Storage) GetType() Service { + return SERVICE_AWS_S3 +} + +func (s3 *AWSS3Storage) GetMetadata() map[string]interface{} { + return map[string]interface{}{ + "account": s3.AccountID, + "region": s3.Region, + "bucketName": s3.BucketName, + "currentState": s3.State, + "tags": s3.Tags, + } +} + +func (s3 *AWSS3Storage) GetLogger() *log.Logger { + return s3.logger +} + +type AWSS3StorageTaggableResource struct { + AWSS3Storage +} + +func (s *AWSS3StorageTaggableResource) NewFromEventBus(event events.CloudWatchEvent, ctx context.Context, passsedInMetadata map[string]interface{}) error { defaultMetadata, err := DefaultLambdaMetadata(ctx) if err != nil { return err @@ -95,7 +150,7 @@ func (s *AWSS3Storage) NewFromEventBus(event events.CloudWatchEvent, ctx context return err } -func (s *AWSS3Storage) NewFromPassableResource(resource PassableResource, ctx context.Context, passsedInMetadata map[string]interface{}) error { +func (s *AWSS3StorageTaggableResource) NewFromPassableResource(resource PassableResource, ctx context.Context, passsedInMetadata map[string]interface{}) error { lamdbaMetadata, err := LambdaMetadataFromPassableResource(ctx, resource) if err != nil { return err @@ -135,7 +190,7 @@ func (s *AWSS3Storage) NewFromPassableResource(resource PassableResource, ctx co return nil } -func (s *AWSS3Storage) Audit() (Action, error) { +func (s *AWSS3StorageTaggableResource) Audit() (Action, error) { var result Action requiredTags := settings.GetString("auditing.required_tags") @@ -160,58 +215,7 @@ func (s *AWSS3Storage) Audit() (Action, error) { return result, nil } -func (s *AWSS3Storage) PublishState() error { - s.logger.WithFields(logrus.Fields{ - "cloud-inquisitor-resource": "aws-s3", - }).Debugf("PublishState: %#v", *s) - return nil -} - -func (s *AWSS3Storage) RefreshState() error { - var AWSInputSession = session.Must(session.NewSession()) - assumedSession, err := AWSAssumeRole(s.AccountID, "ROLE_NAME", AWSInputSession) - if err != nil { - return err - } - - s3Client := s3.New(assumedSession) - - taggingInput := &s3.GetBucketTaggingInput{ - Bucket: aws.String(s.BucketName), - } - - tagsResult, err := s3Client.GetBucketTagging(taggingInput) - if err != nil { - return nil - } - - s.Tags = map[string]string{} - for _, tag := range tagsResult.TagSet { - s.Tags[*tag.Key] = *tag.Value - } - return nil -} - -func (s *AWSS3Storage) SendLogs() error { - s.logger.WithFields(logrus.Fields{ - "cloud-inquisitor-resource": "aws-s3", - }).Debugf("SendLogs: %#v", *s) - return nil -} - -func (s *AWSS3Storage) SendMetrics() error { - s.logger.WithFields(logrus.Fields{ - "cloud-inquisitor-resource": "aws-s3", - }).Debugf("SendMetrics: %#v", *s) - return nil -} - -func (s *AWSS3Storage) SendNotification() error { - s.logger.WithFields(logrus.Fields{"cloud-inquisitor-resource": "aws-s3"}).Debugf("SendNotifcation: %#v", *s) - return nil -} - -func (s *AWSS3Storage) TakeAction(action Action) error { +func (s *AWSS3StorageTaggableResource) TakeAction(action Action) error { actionMode := settings.GetString("actions.mode") s.logger.WithFields(logrus.Fields{ "cloud-inquisitor-resource": "aws-s3", @@ -318,29 +322,11 @@ func (s *AWSS3Storage) TakeAction(action Action) error { return nil } -func (s3 *AWSS3Storage) GetType() Service { - return SERVICE_AWS_S3 -} - -func (s3 *AWSS3Storage) GetMetadata() map[string]interface{} { - return map[string]interface{}{ - "account": s3.AccountID, - "region": s3.Region, - "bucketName": s3.BucketName, - "currentState": s3.State, - "tags": s3.Tags, - } -} - -func (s3 *AWSS3Storage) GetLogger() *log.Logger { - return s3.logger -} - -func (s3 *AWSS3Storage) GetTags() map[string]string { +func (s3 *AWSS3StorageTaggableResource) GetTags() map[string]string { return s3.Tags } -func (s3 *AWSS3Storage) GetMissingTags() []string { +func (s3 *AWSS3StorageTaggableResource) GetMissingTags() []string { requiredTags := settings.GetString("auditing.required_tags") missingTags := []string{} for _, tag := range strings.Split(requiredTags, ",") { @@ -351,3 +337,209 @@ func (s3 *AWSS3Storage) GetMissingTags() []string { return missingTags } + +func (s *AWSS3StorageTaggableResource) RefreshState() error { + var AWSInputSession = session.Must(session.NewSession()) + assumedSession, err := AWSAssumeRole(s.AccountID, "ROLE_NAME", AWSInputSession) + if err != nil { + return err + } + + s3Client := s3.New(assumedSession) + + taggingInput := &s3.GetBucketTaggingInput{ + Bucket: aws.String(s.BucketName), + } + + tagsResult, err := s3Client.GetBucketTagging(taggingInput) + if err != nil { + return nil + } + + s.Tags = map[string]string{} + for _, tag := range tagsResult.TagSet { + s.Tags[*tag.Key] = *tag.Value + } + return nil +} + +type AWSS3StorageHijackableResource struct { + AWSS3Storage +} + +func (s *AWSS3StorageHijackableResource) NewFromEventBus(event events.CloudWatchEvent, ctx context.Context, passsedInMetadata map[string]interface{}) error { + defaultMetadata, err := DefaultLambdaMetadata(ctx) + if err != nil { + return err + } + + mergedMetaData := map[string]interface{}{} + for k, v := range passsedInMetadata { + mergedMetaData[k] = v + } + for k, v := range defaultMetadata { + mergedMetaData[k] = v + } + + opts := log.LoggerOpts{ + Level: log.LogrusLevelConv(settings.GetString("log_level")), + Metadata: mergedMetaData, + } + s.logger = instrument.GetInstrumentedLogger(opts, ctx) + + s.logger.WithFields(mergedMetaData).Debug("marshalling new event") + eventBytes, err := json.Marshal(event) + if err != nil { + s.logger.Error("error marshalling: "+err.Error(), nil) + return err + } + + s.logger.Debug("unmarshalling event to openapi s3 event", nil) + var s3OpenAPI openapis3.AwsEvent + err = json.Unmarshal(eventBytes, &s3OpenAPI) + if err != nil { + s.logger.Error("error unmarshalling: "+err.Error(), nil) + return err + } + + s.State = 0 + s.AccountID = s3OpenAPI.Account + s.Region = s3OpenAPI.Region + s.BucketName = s3OpenAPI.Detail.RequestParameters.BucketName + s.EventName = s3OpenAPI.Detail.EventName + + err = s.RefreshState() + if err == nil { + s.logger.WithFields(s.GetMetadata()).Info("new resource created") + } + return err +} + +func (s *AWSS3StorageHijackableResource) NewFromPassableResource(resource PassableResource, ctx context.Context, passsedInMetadata map[string]interface{}) error { + lamdbaMetadata, err := LambdaMetadataFromPassableResource(ctx, resource) + if err != nil { + return err + + } + + mergedMetaData := map[string]interface{}{} + for k, v := range passsedInMetadata { + mergedMetaData[k] = v + } + for k, v := range lamdbaMetadata { + mergedMetaData[k] = v + } + opts := log.LoggerOpts{ + Level: log.LogrusLevelConv(settings.GetString("log_level")), + Metadata: mergedMetaData, + } + s.logger = instrument.GetInstrumentedLogger(opts, ctx) + structJson, err := json.Marshal(resource.Resource) + if err != nil { + s.logger.WithFields(logrus.Fields{ + "cloud-inquisitor-resource": "aws-s3", + "cloud-inquisitor-error": "json marshal error", + }).Error(err.Error(), nil) + return errors.New("unable to marshal aws-s3 resource") + } + + err = json.Unmarshal(structJson, s) + if err != nil { + s.logger.WithFields(logrus.Fields{ + "cloud-inquisitor-resource": "aws-s3", + "cloud-inquisitor-error": "json unmarshal error", + }).Error(err.Error(), nil) + return errors.New("unable to unmarshal aws-s3 resource") + } + + return nil +} + +func (s *AWSS3StorageHijackableResource) PublishState() error { + db, err := graph.NewDBConnection() + if err != nil { + s.GetLogger().Errorf("error getting db connection: %v", err.Error()) + return err + } + switch s.EventName { + case "CreateBucket": + // get/create account + var account model.Account + err = db.Where(model.Account{AccountID: s.AccountID}).FirstOrCreate(&account).Error + if err != nil { + s.GetLogger().Errorf("unable to create account in graph: %v", err.Error()) + return err + } + // get/create s3 bucket + s3bucket := model.S3{ + AccountID: account.ID, + S3ID: s.BucketName, + CName: fmt.Sprintf("%s.s3.%s.amazonaws.com", s.BucketName, s.Region), + Region: s.Region, + } + err = db.Where(s3bucket).FirstOrCreate(&s3bucket).Error + if err != nil { + s.GetLogger().Errorf("error creating s3 record %#v: %v", s3bucket, err.Error()) + return err + } + case "DeleteBucket": + // get account + var account model.Account + err = db.Where(model.Account{AccountID: s.AccountID}).FirstOrCreate(&account).Error + if err != nil { + s.GetLogger().Errorf("unable to create account in graph: %v", err.Error()) + return err + } + // delete bucket/account combo + s3bucket := model.S3{ + AccountID: account.ID, + S3ID: s.BucketName, + CName: fmt.Sprintf("%s.s3.%s.amazonaws.com", s.BucketName, s.Region), + Region: s.Region, + } + err = db.Where(s3bucket).Delete(&s3bucket).Error + if err != nil { + s.GetLogger().Errorf("error deleting s3 record %#v: %v", s3bucket, err.Error()) + return err + } + default: + s.GetLogger().Errorf("unknown event name for publishing state: %v", s.EventName) + return fmt.Errorf("unknown event name for publishing state: %v", s.EventName) + } + return nil +} + +func (s *AWSS3StorageHijackableResource) AnalyzeForHijack() (*model.HijackableResourceChain, error) { + switch s.EventName { + case "CreateBucket": + // s3 in a sink and so creation in itself + // should not lead to a hijack + return nil, nil + case "DeleteBucket": + // deletetion of a bucket could lead to a number + ctx := context.Background() + id := fmt.Sprintf("s3Bucket-%s-%s", s.Region, s.BucketName) + bucketCName := fmt.Sprintf("%s.s3.%s.amazonaws.com", s.BucketName, s.Region) + resolver, err := graph.NewResolver() + if err != nil { + s.GetLogger().Errorf("error getting resolver to analyze hijacks: %v", err.Error()) + return nil, err + } + + chain, err := resolver.Query().HijackChainByDomain(ctx, id, []string{bucketCName}, model.TypeS3) + if err != nil { + s.GetLogger().Errorf("error analyzing for s3 hijack for event %v: %v", s.EventName, err.Error()) + } + + return chain, err + + default: + s.GetLogger().Errorf("unknown event name for analyzing hijack: %v", s.EventName) + return nil, fmt.Errorf("unknown event name for analyzing hijack: %v", s.EventName) + } + return nil, nil +} + +func (s *AWSS3StorageHijackableResource) RefreshState() error { + return nil +} diff --git a/cloud-inquisitor/graph/generated/generated.go b/cloud-inquisitor/graph/generated/generated.go index ff38a28..c5e0ff7 100644 --- a/cloud-inquisitor/graph/generated/generated.go +++ b/cloud-inquisitor/graph/generated/generated.go @@ -112,6 +112,9 @@ type ComplexityRoot struct { PointedAtByRecords func(childComplexity int, domain string) int Record func(childComplexity int, id string) int Records func(childComplexity int) int + S3BucketByCName func(childComplexity int, cname string) int + S3BucketByName func(childComplexity int, id string) int + S3Buckets func(childComplexity int) int Value func(childComplexity int, id string) int Values func(childComplexity int) int Zone func(childComplexity int, id string) int @@ -125,6 +128,12 @@ type ComplexityRoot struct { Values func(childComplexity int) int } + S3 struct { + CName func(childComplexity int) int + Region func(childComplexity int) int + S3ID func(childComplexity int) int + } + Value struct { ValueID func(childComplexity int) int } @@ -169,6 +178,9 @@ type QueryResolver interface { ElasticbeanstalkEnvironments(ctx context.Context) ([]*model.ElasticbeanstalkEnvironment, error) ElasticbeanstalkByEndpoint(ctx context.Context, endpoint string) (*model.ElasticbeanstalkEnvironment, error) GetElasticbeanstalkUpstreamHijack(ctx context.Context, endpoints []string) ([]*model.HijackableResource, error) + S3Buckets(ctx context.Context) ([]*model.S3, error) + S3BucketByName(ctx context.Context, id string) (*model.S3, error) + S3BucketByCName(ctx context.Context, cname string) (*model.S3, error) HijackChainByDomain(ctx context.Context, id string, domains []string, typeArg model.Type) (*model.HijackableResourceChain, error) } type RecordResolver interface { @@ -562,6 +574,37 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.Records(childComplexity), true + case "Query.s3BucketByCName": + if e.complexity.Query.S3BucketByCName == nil { + break + } + + args, err := ec.field_Query_s3BucketByCName_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Query.S3BucketByCName(childComplexity, args["cname"].(string)), true + + case "Query.s3BucketByName": + if e.complexity.Query.S3BucketByName == nil { + break + } + + args, err := ec.field_Query_s3BucketByName_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Query.S3BucketByName(childComplexity, args["id"].(string)), true + + case "Query.s3Buckets": + if e.complexity.Query.S3Buckets == nil { + break + } + + return e.complexity.Query.S3Buckets(childComplexity), true + case "Query.value": if e.complexity.Query.Value == nil { break @@ -628,6 +671,27 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Record.Values(childComplexity), true + case "S3.cname": + if e.complexity.S3.CName == nil { + break + } + + return e.complexity.S3.CName(childComplexity), true + + case "S3.region": + if e.complexity.S3.Region == nil { + break + } + + return e.complexity.S3.Region(childComplexity), true + + case "S3.s3ID": + if e.complexity.S3.S3ID == nil { + break + } + + return e.complexity.S3.S3ID(childComplexity), true + case "Value.valueID": if e.complexity.Value.ValueID == nil { break @@ -732,6 +796,7 @@ var sources = []*ast.Source{ DISTRIBUTION ORIGIN ELASTICBEANSTALK + S3 } type Account { @@ -787,6 +852,12 @@ type ElasticbeanstalkEnvironment { region: String! } +type S3 { + s3ID: ID! + region: String! + cname: String! +} + type HijackableResource { id: ID! account: String! @@ -831,8 +902,11 @@ type Query { elasticbeanstalkByEndpoint(endpoint: String!): ElasticbeanstalkEnvironment! getElasticbeanstalkUpstreamHijack(endpoints: [String!]!): [HijackableResource!]! + s3Buckets: [S3!]! + s3BucketByName(id: ID!): S3! + s3BucketByCName(cname: String!): S3! + hijackChainByDomain(id: ID!, domains: [String!]!, type: Type!): HijackableResourceChain! - }`, BuiltIn: false}, } var parsedSchema = gqlparser.MustLoadSchema(sources...) @@ -1039,6 +1113,34 @@ func (ec *executionContext) field_Query_record_args(ctx context.Context, rawArgs return args, nil } +func (ec *executionContext) field_Query_s3BucketByCName_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 string + if tmp, ok := rawArgs["cname"]; ok { + arg0, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["cname"] = arg0 + return args, nil +} + +func (ec *executionContext) field_Query_s3BucketByName_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 string + if tmp, ok := rawArgs["id"]; ok { + arg0, err = ec.unmarshalNID2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["id"] = arg0 + return args, nil +} + func (ec *executionContext) field_Query_value_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -2803,6 +2905,122 @@ func (ec *executionContext) _Query_getElasticbeanstalkUpstreamHijack(ctx context return ec.marshalNHijackableResource2ᚕᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐHijackableResourceᚄ(ctx, field.Selections, res) } +func (ec *executionContext) _Query_s3Buckets(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().S3Buckets(rctx) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]*model.S3) + fc.Result = res + return ec.marshalNS32ᚕᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐS3ᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) _Query_s3BucketByName(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Query_s3BucketByName_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().S3BucketByName(rctx, args["id"].(string)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*model.S3) + fc.Result = res + return ec.marshalNS32ᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐS3(ctx, field.Selections, res) +} + +func (ec *executionContext) _Query_s3BucketByCName(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Query_s3BucketByCName_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().S3BucketByCName(rctx, args["cname"].(string)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*model.S3) + fc.Result = res + return ec.marshalNS32ᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐS3(ctx, field.Selections, res) +} + func (ec *executionContext) _Query_hijackChainByDomain(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -3049,6 +3267,108 @@ func (ec *executionContext) _Record_alias(ctx context.Context, field graphql.Col return ec.marshalNBoolean2bool(ctx, field.Selections, res) } +func (ec *executionContext) _S3_s3ID(ctx context.Context, field graphql.CollectedField, obj *model.S3) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "S3", + Field: field, + Args: nil, + IsMethod: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.S3ID, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNID2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _S3_region(ctx context.Context, field graphql.CollectedField, obj *model.S3) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "S3", + Field: field, + Args: nil, + IsMethod: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Region, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _S3_cname(ctx context.Context, field graphql.CollectedField, obj *model.S3) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "S3", + Field: field, + Args: nil, + IsMethod: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.CName, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + func (ec *executionContext) _Value_valueID(ctx context.Context, field graphql.CollectedField, obj *model.Value) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -4958,6 +5278,48 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr } return res }) + case "s3Buckets": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_s3Buckets(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) + case "s3BucketByName": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_s3BucketByName(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) + case "s3BucketByCName": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_s3BucketByCName(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "hijackChainByDomain": field := field out.Concurrently(i, func() (res graphql.Marshaler) { @@ -5038,6 +5400,43 @@ func (ec *executionContext) _Record(ctx context.Context, sel ast.SelectionSet, o return out } +var s3Implementors = []string{"S3"} + +func (ec *executionContext) _S3(ctx context.Context, sel ast.SelectionSet, obj *model.S3) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, s3Implementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("S3") + case "s3ID": + out.Values[i] = ec._S3_s3ID(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "region": + out.Values[i] = ec._S3_region(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "cname": + out.Values[i] = ec._S3_cname(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + var valueImplementors = []string{"Value"} func (ec *executionContext) _Value(ctx context.Context, sel ast.SelectionSet, obj *model.Value) graphql.Marshaler { @@ -5774,6 +6173,57 @@ func (ec *executionContext) marshalNRecord2ᚖgithubᚗcomᚋRiotGamesᚋcloud return ec._Record(ctx, sel, v) } +func (ec *executionContext) marshalNS32githubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐS3(ctx context.Context, sel ast.SelectionSet, v model.S3) graphql.Marshaler { + return ec._S3(ctx, sel, &v) +} + +func (ec *executionContext) marshalNS32ᚕᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐS3ᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.S3) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNS32ᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐS3(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + return ret +} + +func (ec *executionContext) marshalNS32ᚖgithubᚗcomᚋRiotGamesᚋcloudᚑinquisitorᚋcloudᚑinquisitorᚋgraphᚋmodelᚐS3(ctx context.Context, sel ast.SelectionSet, v *model.S3) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + return ec._S3(ctx, sel, v) +} + func (ec *executionContext) unmarshalNString2string(ctx context.Context, v interface{}) (string, error) { return graphql.UnmarshalString(v) } diff --git a/cloud-inquisitor/graph/model/models_gen.go b/cloud-inquisitor/graph/model/models_gen.go index 3b210b4..854d92d 100644 --- a/cloud-inquisitor/graph/model/models_gen.go +++ b/cloud-inquisitor/graph/model/models_gen.go @@ -31,6 +31,7 @@ const ( TypeDistribution Type = "DISTRIBUTION" TypeOrigin Type = "ORIGIN" TypeElasticbeanstalk Type = "ELASTICBEANSTALK" + TypeS3 Type = "S3" ) var AllType = []Type{ @@ -40,11 +41,12 @@ var AllType = []Type{ TypeDistribution, TypeOrigin, TypeElasticbeanstalk, + TypeS3, } func (e Type) IsValid() bool { switch e { - case TypeAccount, TypeZone, TypeRecord, TypeDistribution, TypeOrigin, TypeElasticbeanstalk: + case TypeAccount, TypeZone, TypeRecord, TypeDistribution, TypeOrigin, TypeElasticbeanstalk, TypeS3: return true } return false diff --git a/cloud-inquisitor/graph/model/s3.go b/cloud-inquisitor/graph/model/s3.go new file mode 100644 index 0000000..212be35 --- /dev/null +++ b/cloud-inquisitor/graph/model/s3.go @@ -0,0 +1,13 @@ +package model + +import ( + "github.com/jinzhu/gorm" +) + +type S3 struct { + gorm.Model + AccountID uint + S3ID string `json:"s3ID"` + CName string `json:"cname"` + Region string `json:"region"` +} diff --git a/cloud-inquisitor/graph/schema.graphqls b/cloud-inquisitor/graph/schema.graphqls index 74ba5c8..769b66c 100644 --- a/cloud-inquisitor/graph/schema.graphqls +++ b/cloud-inquisitor/graph/schema.graphqls @@ -5,6 +5,7 @@ enum Type { DISTRIBUTION ORIGIN ELASTICBEANSTALK + S3 } type Account { @@ -60,6 +61,12 @@ type ElasticbeanstalkEnvironment { region: String! } +type S3 { + s3ID: ID! + region: String! + cname: String! +} + type HijackableResource { id: ID! account: String! @@ -104,6 +111,9 @@ type Query { elasticbeanstalkByEndpoint(endpoint: String!): ElasticbeanstalkEnvironment! getElasticbeanstalkUpstreamHijack(endpoints: [String!]!): [HijackableResource!]! + s3Buckets: [S3!]! + s3BucketByName(id: ID!): S3! + s3BucketByCName(cname: String!): S3! + hijackChainByDomain(id: ID!, domains: [String!]!, type: Type!): HijackableResourceChain! - } \ No newline at end of file diff --git a/cloud-inquisitor/graph/schema.resolvers.go b/cloud-inquisitor/graph/schema.resolvers.go index 345c0d0..9d7d601 100644 --- a/cloud-inquisitor/graph/schema.resolvers.go +++ b/cloud-inquisitor/graph/schema.resolvers.go @@ -557,6 +557,56 @@ func (r *queryResolver) GetElasticbeanstalkUpstreamHijack(ctx context.Context, e return hijackChain, nil } +func (r *queryResolver) S3Buckets(ctx context.Context) ([]*model.S3, error) { + log.Debug("getting all s3 buckets") + + var buckets []*model.S3 + err := r.DB.Find(&buckets).Error + if err != nil { + log.Errorf("error getting all s3 buckets: %v", err.Error()) + return []*model.S3{}, err + } + + if log.GetLevel() == log.DebugLevel { + log.Debugf("found s3 buckets: ") + for idx, bucket := range buckets { + log.Debugf("%v: %#v", idx, *bucket) + } + } + + return buckets, nil +} + +func (r *queryResolver) S3BucketByName(ctx context.Context, id string) (*model.S3, error) { + log.Debugf("looking for bucket with name: %s", id) + + bucket := model.S3{ + S3ID: id, + } + + err := r.DB.Where(bucket).First(&bucket).Error + if err != nil { + log.Errorf("error looking for bucket with name %s: %v", id, err.Error()) + } + + return &bucket, err +} + +func (r *queryResolver) S3BucketByCName(ctx context.Context, cname string) (*model.S3, error) { + log.Debugf("looking for bucket with cname: %s", cname) + + bucket := model.S3{ + CName: cname, + } + + err := r.DB.Where(bucket).First(&bucket).Error + if err != nil { + log.Errorf("error looking for bucket with name %s: %v", cname, err.Error()) + } + + return &bucket, err +} + func (r *queryResolver) HijackChainByDomain(ctx context.Context, id string, domains []string, typeArg model.Type) (*model.HijackableResourceChain, error) { switch typeArg { case model.TypeElasticbeanstalk: @@ -598,6 +648,80 @@ func (r *queryResolver) HijackChainByDomain(ctx context.Context, id string, doma Upstream: chain, Downstream: []*model.HijackableResource{}, }, err + + case model.TypeS3: + /* + ID string `json:"id"` + Account string `json:"account"` + Type Type `json:"type"` + Value *Value `json:"value"` + */ + bucket, err := r.Query().S3BucketByCName(ctx, domains[0]) + if err != nil { + log.Errorf("unable to find S3 bucket for domain %#v: %v", domains, err.Error()) + return nil, err + } + + var account model.Account + err = r.DB.First(&account, bucket.AccountID).Error + if err != nil { + log.Errorf("unable to find account for S3 bucket %s: %v", bucket.S3ID, err.Error()) + return nil, err + } + + // check for resources that could front S3 + // couldfront + distros, err := r.Query().PointedAtByDistribution(ctx, domains[0]) + if err != nil { + log.Errorf("error finding all cloudfront distributions fronting s3 bucket %v: %v", domains[0], err.Error()) + } + + // route53 + records, err := r.Query().PointedAtByRecords(ctx, domains[0]) + if err != nil { + log.Errorf("error finding all route53 records that point to s3 bucket %v: %v", domains[0], err.Error()) + } + + upstreamChain := make([]*model.HijackableResource, 0) + for _, cfDistro := range distros { + var distroAccount model.Account + err := r.DB.First(&distroAccount, cfDistro.AccountID).Error + if err != nil { + log.Errorf("unable to find account for cloudfront distribtion %v: %v", cfDistro.DistributionID, err.Error()) + } + upstreamChain = append(upstreamChain, &model.HijackableResource{ + ID: cfDistro.DistributionID, + Account: distroAccount.AccountID, + Type: model.TypeDistribution, + Value: &model.Value{ + ValueID: cfDistro.Domain, + }, + }) + } + + for _, record := range records { + var recordAccount model.Account + err := r.DB.First(&recordAccount, record.AccountID).Error + if err != nil { + log.Errorf("unable to find account for record %v: %v", record.RecordID, err.Error()) + } + + upstreamChain = append(upstreamChain, &model.HijackableResource{}) + } + + return &model.HijackableResourceChain{ + ID: id, + Resource: &model.HijackableResource{ + ID: id, + Account: account.AccountID, + Type: model.TypeS3, + Value: &model.Value{ + ValueID: bucket.CName, + }, + }, + Upstream: upstreamChain, + Downstream: []*model.HijackableResource{}, + }, err } return &model.HijackableResourceChain{}, nil diff --git a/cloud-inquisitor/resource.go b/cloud-inquisitor/resource.go index 92ee05d..a8d52ee 100644 --- a/cloud-inquisitor/resource.go +++ b/cloud-inquisitor/resource.go @@ -50,6 +50,7 @@ type Resource interface { GetType() Service // GetMetadata returns a map of Resoruce metadata GetMetadata() map[string]interface{} + GetLogger() *log.Logger } @@ -88,7 +89,7 @@ func (p PassableResource) GetTaggableResource(ctx context.Context, metadata map[ err := rds.NewFromPassableResource(p, ctx, metadata) return rds, err case SERVICE_AWS_S3: - s3 := &AWSS3Storage{} + s3 := &AWSS3StorageTaggableResource{} err := s3.NewFromPassableResource(p, ctx, metadata) return s3, err default: @@ -110,7 +111,7 @@ func NewTaggableResource(event events.CloudWatchEvent, ctx context.Context, meta return resource, err case "aws.s3": - resource = &AWSS3Storage{} + resource = &AWSS3StorageTaggableResource{} err := resource.NewFromEventBus(event, ctx, metadata) return resource, err diff --git a/cloud-inquisitor/resource_hijackable.go b/cloud-inquisitor/resource_hijackable.go index 61f1379..283c9c9 100644 --- a/cloud-inquisitor/resource_hijackable.go +++ b/cloud-inquisitor/resource_hijackable.go @@ -42,6 +42,10 @@ func (p PassableResource) GetHijackableResource(ctx context.Context, metadata ma eb := &AWSElasticBeanstalkEnvironmentHijackableResource{} err := eb.NewFromPassableResource(p, ctx, metadata) return eb, err + case SERVICE_AWS_S3: + s3 := &AWSS3StorageHijackableResource{} + err := s3.NewFromPassableResource(p, ctx, metadata) + return s3, err default: return nil, errors.New("no matching resource for type " + p.Type) } @@ -98,6 +102,26 @@ func NewHijackableResource(event events.CloudWatchEvent, ctx context.Context, me resource = &StubResource{} return resource, errors.New("unable to parse evetName from map") } + case "aws.s3": + detailMap := map[string]interface{}{} + err := json.Unmarshal(event.Detail, &detailMap) + if err != nil { + return resource, err + } + if eventName, ok := detailMap["eventName"]; ok { + switch eventName { + case "CreateBucket", "DeleteBucket": + resource = &AWSS3StorageHijackableResource{} + resourceErr := resource.NewFromEventBus(event, ctx, metadata) + return resource, resourceErr + default: + resource = &StubResource{} + return resource, errors.New("unknown s3 eventName") + } + } else { + resource = &StubResource{} + return resource, errors.New("unable to parse evetName from map") + } default: resource = &StubResource{} err := resource.NewFromEventBus(event, ctx, metadata) diff --git a/go.mod b/go.mod index b3bcc72..a6569a9 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( golang.org/x/net v0.0.0-20200625001655-4c5254603344 // indirect golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 // indirect - golang.org/x/sys v0.0.0-20200915084602-288bc346aa39 // indirect + golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20 // indirect golang.org/x/text v0.3.3 // indirect google.golang.org/appengine v1.6.6 // indirect google.golang.org/genproto v0.0.0-20200626011028-ee7919e894b5 // indirect diff --git a/go.sum b/go.sum index 2d72296..7fa3b75 100644 --- a/go.sum +++ b/go.sum @@ -543,6 +543,10 @@ golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 h1:W0lCpv29Hv0UaM1LXb9QlBHLN golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200915084602-288bc346aa39 h1:356XA7ITklAU2//sYkjFeco+dH1bCRD8XCJ9FIEsvo4= golang.org/x/sys v0.0.0-20200915084602-288bc346aa39/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200916084744-dbad9cb7cb7a h1:chkwkn8HYWVtTE5DCQNKYlkyptadXYY0+PuyaVdyMo4= +golang.org/x/sys v0.0.0-20200916084744-dbad9cb7cb7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20 h1:4X356008q5SA3YXu8PiRap39KFmy4Lf6sGlceJKZQsU= +golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= diff --git a/terraform_modules/event_rule/event_rule.tf b/terraform_modules/event_rule/event_rule.tf index 16bccb6..dcae695 100644 --- a/terraform_modules/event_rule/event_rule.tf +++ b/terraform_modules/event_rule/event_rule.tf @@ -4,6 +4,7 @@ locals { "route53_dns_hijacks" = file("${path.module}/patterns/route53_dns_hijacks.json") "cloudfront_dns_hijacks" = file("${path.module}/patterns/cloudfront_dns_hijacks.json") "elasticbeanstalk_dns_hijacks" = file("${path.module}/patterns/elasticbeanstalk_dns_hijacks.json") + "s3_dns_hijacks" = file("${path.module}/patterns/s3_dns_hijacks.json") } ) } diff --git a/terraform_modules/event_rule/patterns/s3_dns_hijack.json b/terraform_modules/event_rule/patterns/s3_dns_hijack.json new file mode 100644 index 0000000..4e6a77d --- /dev/null +++ b/terraform_modules/event_rule/patterns/s3_dns_hijack.json @@ -0,0 +1,17 @@ +{ + "source": [ + "aws.s3" + ], + "detail-type": [ + "AWS API Call via CloudTrail" + ], + "detail": { + "eventSource": [ + "s3.amazonaws.com" + ], + "eventName": [ + "CreateBucket", + "DeleteBucket" + ] + } +} \ No newline at end of file From 9562f19b66e0e9963a815f07892d23a5aa408ccc Mon Sep 17 00:00:00 2001 From: Jesse Taylor Date: Tue, 22 Sep 2020 13:28:31 -0700 Subject: [PATCH 9/9] added in S3 hijacks removal and validated manually using stepfunction UI --- cloud-inquisitor/graph/resolver.go | 6 ++ cloud-inquisitor/graph/schema.resolvers.go | 38 +++++++- .../serverless/hijack_graph_analyzer/main.go | 92 +++++++++---------- .../serverless/hijack_initializer/main.go | 2 +- go.mod | 2 +- go.sum | 2 + ...s3_dns_hijack.json => s3_dns_hijacks.json} | 0 7 files changed, 90 insertions(+), 52 deletions(-) rename terraform_modules/event_rule/patterns/{s3_dns_hijack.json => s3_dns_hijacks.json} (100%) diff --git a/cloud-inquisitor/graph/resolver.go b/cloud-inquisitor/graph/resolver.go index b23a81a..2dd2a35 100644 --- a/cloud-inquisitor/graph/resolver.go +++ b/cloud-inquisitor/graph/resolver.go @@ -106,6 +106,10 @@ func CreateTables() error { db.CreateTable(&model.ElasticbeanstalkEnvironment{}) } + if !db.HasTable(&model.S3{}) { + db.CreateTable(&model.S3{}) + } + return nil } @@ -128,6 +132,7 @@ func DropTables() error { &model.Origin{}, &model.OriginGroup{}, &model.ElasticbeanstalkEnvironment{}, + &model.S3{}, ).Error if err != nil { @@ -156,6 +161,7 @@ func MigrateTables() error { &model.Origin{}, &model.OriginGroup{}, &model.ElasticbeanstalkEnvironment{}, + &model.S3{}, ).Error if err != nil { diff --git a/cloud-inquisitor/graph/schema.resolvers.go b/cloud-inquisitor/graph/schema.resolvers.go index 9d7d601..6086429 100644 --- a/cloud-inquisitor/graph/schema.resolvers.go +++ b/cloud-inquisitor/graph/schema.resolvers.go @@ -252,6 +252,21 @@ func (r *queryResolver) PointedAtByRecords(ctx context.Context, domain string) ( return []*model.Record{}, err } + for idx, record := range records { + recordValues, recordErr := r.Resolver.Record().Values(ctx, record) + if recordErr != nil { + log.Errorf("unable to resolve values for record: %v", record.RecordID) + continue + } + + value := make([]model.Value, len(recordValues)) + for valIdx, val := range recordValues { + value[valIdx] = *val + } + + records[idx].ValueRelation = value + } + if log.GetLevel() == log.DebugLevel { for _, record := range records { log.Debugf("record %#v points to domain %v", record, domain) @@ -706,7 +721,28 @@ func (r *queryResolver) HijackChainByDomain(ctx context.Context, id string, doma log.Errorf("unable to find account for record %v: %v", record.RecordID, err.Error()) } - upstreamChain = append(upstreamChain, &model.HijackableResource{}) + recordValues, err := r.Resolver.Record().Values(ctx, record) + if err != nil { + log.Errorf("error resolving values for record for record: %v", record.RecordID) + } + var value *model.Value = nil + if len(recordValues) > 0 { + value = recordValues[0] + + } + + if len(recordValues) > 1 { + log.Warnf("record has more than one value pointing to resource: %v", record.RecordID) + } + + upstreamChain = append(upstreamChain, &model.HijackableResource{ + ID: record.RecordID, + Account: recordAccount.AccountID, + Type: model.TypeRecord, + Value: &model.Value{ + ValueID: value.ValueID, + }, + }) } return &model.HijackableResourceChain{ diff --git a/cloud-inquisitor/serverless/hijack_graph_analyzer/main.go b/cloud-inquisitor/serverless/hijack_graph_analyzer/main.go index 1d58ad5..37c026a 100644 --- a/cloud-inquisitor/serverless/hijack_graph_analyzer/main.go +++ b/cloud-inquisitor/serverless/hijack_graph_analyzer/main.go @@ -31,58 +31,52 @@ func handlerRequest(ctx context.Context, resource cloudinquisitor.PassableResour }, err } - logger := parsedResource.GetLogger() - if logger.L.GetLevel() == logrus.DebugLevel { - logger.Debugf("hijack chain for resource with metadata: %#v", parsedResource.GetMetadata()) - logger.Debugf("root hijack element: %#v", *hijackChain.Resource) - for idx, hijackElement := range hijackChain.Upstream { - logger.Debugf("upstream hijack element %v: %#v", idx, *hijackElement) - } - for idx, hijackElement := range hijackChain.Downstream { - logger.Debugf("downstream hijack element %v: %#v", idx, *hijackElement) - } - } + if hijackChain != nil { + if hijackChain.Resource != nil { + if len(hijackChain.Upstream) > 0 || len(hijackChain.Downstream) > 0 { + logger := parsedResource.GetLogger() + if logger.L.GetLevel() == logrus.DebugLevel { + logger.Debugf("hijack chain for resource with metadata: %#v", parsedResource.GetMetadata()) + logger.Debugf("root hijack element: %#v", *hijackChain.Resource) + for idx, hijackElement := range hijackChain.Upstream { + logger.Debugf("upstream hijack element %v: %#v", idx, *hijackElement) + } + for idx, hijackElement := range hijackChain.Downstream { + logger.Debugf("downstream hijack element %v: %#v", idx, *hijackElement) + } + } + // send notification + content := notification.GenerateContent(hijackChain) + sendTo := settings.GetString("simple_email_service.verified_email") + msg, err := notification.NewHijackNotficationMessage("Potential DNS Hijack", []string{sendTo}, []string{sendTo}, content) + if err != nil { + return cloudinquisitor.PassableResource{ + Resource: parsedResource, + Type: parsedResource.GetType(), + Metadata: parsedResource.GetLogger().GetMetadata(), + Finished: true, + }, fmt.Errorf("error generating hijack message: %s", err.Error()) + } - if len(hijackChain.Upstream) > 0 || len(hijackChain.Downstream) > 0 { - // send notification - content := notification.GenerateContent(hijackChain) - sendTo := settings.GetString("simple_email_service.verified_email") - msg, err := notification.NewHijackNotficationMessage("Potential DNS Hijack", []string{sendTo}, []string{sendTo}, content) - if err != nil { - return cloudinquisitor.PassableResource{ - Resource: parsedResource, - Type: parsedResource.GetType(), - Metadata: parsedResource.GetLogger().GetMetadata(), - Finished: true, - }, fmt.Errorf("error generating hijack message: %s", err.Error()) - } + notifier, err := notification.NewNotifier() + if err != nil { + return cloudinquisitor.PassableResource{ + Resource: parsedResource, + Type: parsedResource.GetType(), + Metadata: parsedResource.GetLogger().GetMetadata(), + Finished: true, + }, errors.New("unable to get notifier to notify of potential hijack") + } - notifier, err := notification.NewNotifier() - if err != nil { - return cloudinquisitor.PassableResource{ - Resource: parsedResource, - Type: parsedResource.GetType(), - Metadata: parsedResource.GetLogger().GetMetadata(), - Finished: true, - }, errors.New("unable to get notifier to notify of potential hijack") + _, err = notifier.SendNotification(msg) + return cloudinquisitor.PassableResource{ + Resource: parsedResource, + Type: parsedResource.GetType(), + Metadata: parsedResource.GetLogger().GetMetadata(), + Finished: true, + }, err + } } - - _, err = notifier.SendNotification(msg) - return cloudinquisitor.PassableResource{ - Resource: parsedResource, - Type: parsedResource.GetType(), - Metadata: parsedResource.GetLogger().GetMetadata(), - Finished: true, - }, err - } - - if err != nil { - return cloudinquisitor.PassableResource{ - Resource: parsedResource, - Type: parsedResource.GetType(), - Metadata: parsedResource.GetLogger().GetMetadata(), - Finished: true, - }, err } return cloudinquisitor.PassableResource{ diff --git a/cloud-inquisitor/serverless/hijack_initializer/main.go b/cloud-inquisitor/serverless/hijack_initializer/main.go index ffe8c5c..9df2209 100644 --- a/cloud-inquisitor/serverless/hijack_initializer/main.go +++ b/cloud-inquisitor/serverless/hijack_initializer/main.go @@ -53,7 +53,7 @@ func handlerRequest(ctx context.Context, event events.CloudWatchEvent) (passable Metadata: record.GetLogger().GetMetadata(), }) } - case cloudinquisitor.SERVICE_AWS_ROUTE53_ZONE, cloudinquisitor.SERVICE_AWS_CLOUDFRONT, cloudinquisitor.SERVICE_AWS_ELASTICBEANSTALK: + case cloudinquisitor.SERVICE_AWS_ROUTE53_ZONE, cloudinquisitor.SERVICE_AWS_CLOUDFRONT, cloudinquisitor.SERVICE_AWS_ELASTICBEANSTALK, cloudinquisitor.SERVICE_AWS_S3: passableResources = append(passableResources, cloudinquisitor.PassableResource{ Resource: resource, Type: resource.GetType(), diff --git a/go.mod b/go.mod index a6569a9..7fe09c1 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( golang.org/x/net v0.0.0-20200625001655-4c5254603344 // indirect golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 // indirect - golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20 // indirect + golang.org/x/sys v0.0.0-20200918174421-af09f7315aff // indirect golang.org/x/text v0.3.3 // indirect google.golang.org/appengine v1.6.6 // indirect google.golang.org/genproto v0.0.0-20200626011028-ee7919e894b5 // indirect diff --git a/go.sum b/go.sum index 7fa3b75..8439583 100644 --- a/go.sum +++ b/go.sum @@ -547,6 +547,8 @@ golang.org/x/sys v0.0.0-20200916084744-dbad9cb7cb7a h1:chkwkn8HYWVtTE5DCQNKYlkyp golang.org/x/sys v0.0.0-20200916084744-dbad9cb7cb7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20 h1:4X356008q5SA3YXu8PiRap39KFmy4Lf6sGlceJKZQsU= golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200918174421-af09f7315aff h1:1CPUrky56AcgSpxz/KfgzQWzfG09u5YOL8MvPYBlrL8= +golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= diff --git a/terraform_modules/event_rule/patterns/s3_dns_hijack.json b/terraform_modules/event_rule/patterns/s3_dns_hijacks.json similarity index 100% rename from terraform_modules/event_rule/patterns/s3_dns_hijack.json rename to terraform_modules/event_rule/patterns/s3_dns_hijacks.json