diff --git a/enumeration/alerter/alert.go b/enumeration/alerter/alert.go index 5cb121ac5..32ce075a2 100644 --- a/enumeration/alerter/alert.go +++ b/enumeration/alerter/alert.go @@ -1,12 +1,17 @@ package alerter -import "encoding/json" +import ( + "encoding/json" + + "github.com/snyk/driftctl/enumeration/resource" +) type Alerts map[string][]Alert type Alert interface { Message() string ShouldIgnoreResource() bool + Resource() *resource.Resource } type FakeAlert struct { @@ -22,6 +27,10 @@ func (f *FakeAlert) ShouldIgnoreResource() bool { return f.IgnoreResource } +func (f *FakeAlert) Resource() *resource.Resource { + return nil +} + type SerializableAlert struct { Alert } @@ -38,6 +47,10 @@ func (u *SerializedAlert) ShouldIgnoreResource() bool { return false } +func (s *SerializedAlert) Resource() *resource.Resource { + return nil +} + func (s *SerializableAlert) UnmarshalJSON(bytes []byte) error { var res SerializedAlert diff --git a/enumeration/diagnostic.go b/enumeration/diagnostic.go deleted file mode 100644 index 2e6771f87..000000000 --- a/enumeration/diagnostic.go +++ /dev/null @@ -1,12 +0,0 @@ -package enumeration - -import "github.com/snyk/driftctl/enumeration/resource" - -type Diagnostic interface { - Code() string - Message() string - ResourceType() string - Resource() *resource.Resource -} - -type Diagnostics []Diagnostic diff --git a/enumeration/diagnostic/diagnostic.go b/enumeration/diagnostic/diagnostic.go new file mode 100644 index 000000000..d0a42390d --- /dev/null +++ b/enumeration/diagnostic/diagnostic.go @@ -0,0 +1,54 @@ +package diagnostic + +import ( + "github.com/snyk/driftctl/enumeration/alerter" + "github.com/snyk/driftctl/enumeration/remote/alerts" + "github.com/snyk/driftctl/enumeration/resource" +) + +type Diagnostic interface { + Code() string + Message() string + ResourceType() string + Resource() *resource.Resource +} + +type diagnosticImpl struct { + alert alerter.Alert +} + +func (d *diagnosticImpl) Code() string { + if _, ok := d.alert.(*alerts.RemoteAccessDeniedAlert); ok { + return "ACCESS_DENIED" + } + return "UNKNOWN_ERROR" +} + +func (d *diagnosticImpl) Message() string { + return d.alert.Message() +} + +func (d *diagnosticImpl) ResourceType() string { + ty := "" + if d.Resource() != nil { + ty = d.Resource().ResourceType() + } + return ty +} + +func (d *diagnosticImpl) Resource() *resource.Resource { + return d.alert.Resource() +} + +type Diagnostics []Diagnostic + +func FromAlerts(alertMap alerter.Alerts) Diagnostics { + var results Diagnostics + for _, v := range alertMap { + for _, alert := range v { + diag := &diagnosticImpl{alert} + results = append(results, diag) + } + } + return results +} diff --git a/enumeration/enum.go b/enumeration/enum.go index 3b5c48d4b..5da037bee 100644 --- a/enumeration/enum.go +++ b/enumeration/enum.go @@ -3,6 +3,7 @@ package enumeration import ( "time" + "github.com/snyk/driftctl/enumeration/diagnostic" "github.com/snyk/driftctl/enumeration/resource" ) @@ -24,7 +25,7 @@ type EnumerateOutput struct { // If the diagnostic is associated with a resource type, the ResourceType() // call will indicate which type. If associated with a resource, the Resource() // call will indicate which resource. - Diagnostics Diagnostics + Diagnostics diagnostic.Diagnostics } type Enumerator interface { diff --git a/enumeration/enumerator/cloud_enumerator.go b/enumeration/enumerator/cloud_enumerator.go index 0aebd85ac..1b962168c 100644 --- a/enumeration/enumerator/cloud_enumerator.go +++ b/enumeration/enumerator/cloud_enumerator.go @@ -6,10 +6,10 @@ import ( "os" "sync" - "github.com/snyk/driftctl/enumeration" - "github.com/sirupsen/logrus" + "github.com/snyk/driftctl/enumeration" "github.com/snyk/driftctl/enumeration/alerter" + "github.com/snyk/driftctl/enumeration/diagnostic" "github.com/snyk/driftctl/enumeration/parallel" "github.com/snyk/driftctl/enumeration/remote" "github.com/snyk/driftctl/enumeration/remote/common" @@ -27,6 +27,11 @@ type CloudEnumerator struct { to string } +type ListOutput struct { + Resources []*resource.Resource + Diagnostics diagnostic.Diagnostics +} + type cloudEnumeratorBuilder struct { cloud string providerVersion string @@ -58,7 +63,7 @@ func (b *cloudEnumeratorBuilder) Build() (*CloudEnumerator, error) { detailsFetcherRunner: parallel.NewParallelRunner(context.TODO(), 10), providerLibrary: terraform.NewProviderLibrary(), remoteLibrary: common.NewRemoteLibrary(), - alerter: &sliceAlerter{}, + alerter: newSliceAlerter(), progress: &dummyCounter{}, } @@ -92,6 +97,9 @@ func (e *CloudEnumerator) init(to, providerVersion, configDirectory string) erro } func (e *CloudEnumerator) Enumerate(input *enumeration.EnumerateInput) (*enumeration.EnumerateOutput, error) { + + e.alerter.alerts = alerter.Alerts{} + types := map[string]struct{}{} for _, resourceType := range input.ResourceTypes { types[resourceType] = struct{}{} @@ -135,14 +143,19 @@ func (e *CloudEnumerator) Enumerate(input *enumeration.EnumerateInput) (*enumera mapRes := mapByType(results) + diagnostics := diagnostic.FromAlerts(e.alerter.Alerts()) + return &enumeration.EnumerateOutput{ Resources: mapRes, Timings: nil, - Diagnostics: nil, + Diagnostics: diagnostics, }, nil } func (e *CloudEnumerator) Refresh(input *enumeration.RefreshInput) (*enumeration.RefreshOutput, error) { + + e.alerter.alerts = alerter.Alerts{} + for _, resByType := range input.Resources { for _, res := range resByType { res := res @@ -170,10 +183,11 @@ func (e *CloudEnumerator) Refresh(input *enumeration.RefreshInput) (*enumeration } mapRes := mapByType(results) + diagnostics := diagnostic.FromAlerts(e.alerter.Alerts()) return &enumeration.RefreshOutput{ Resources: mapRes, - Diagnostics: nil, + Diagnostics: diagnostics, }, nil } @@ -203,19 +217,28 @@ loop: return results, runner.Err() } -func (e *CloudEnumerator) List(typ string) ([]*resource.Resource, error) { +func (e *CloudEnumerator) List(typ string) (*ListOutput, error) { + + diagnostics := diagnostic.Diagnostics{} + enumInput := &enumeration.EnumerateInput{ResourceTypes: []string{typ}} enumerate, err := e.Enumerate(enumInput) if err != nil { return nil, err } + diagnostics = append(diagnostics, enumerate.Diagnostics...) refreshInput := &enumeration.RefreshInput{Resources: enumerate.Resources} refresh, err := e.Refresh(refreshInput) if err != nil { return nil, err } - return refresh.Resources[typ], nil + diagnostics = append(diagnostics, refresh.Diagnostics...) + + return &ListOutput{ + Resources: refresh.Resources[typ], + Diagnostics: diagnostics, + }, nil } type sliceAlerter struct { @@ -223,6 +246,12 @@ type sliceAlerter struct { alerts alerter.Alerts } +func newSliceAlerter() *sliceAlerter { + return &sliceAlerter{ + alerts: alerter.Alerts{}, + } +} + func (d *sliceAlerter) Alerts() alerter.Alerts { return d.alerts } diff --git a/enumeration/refresh.go b/enumeration/refresh.go index d3b5b9e27..216ae410d 100644 --- a/enumeration/refresh.go +++ b/enumeration/refresh.go @@ -2,6 +2,7 @@ package enumeration import ( "github.com/hashicorp/terraform/terraform" + "github.com/snyk/driftctl/enumeration/diagnostic" "github.com/snyk/driftctl/enumeration/resource" ) @@ -12,7 +13,7 @@ type RefreshInput struct { type RefreshOutput struct { Resources map[string][]*resource.Resource - Diagnostics Diagnostics + Diagnostics diagnostic.Diagnostics } type GetSchemasOutput struct { diff --git a/enumeration/remote/alerts/alerts.go b/enumeration/remote/alerts/alerts.go index 180369aaf..06670066a 100644 --- a/enumeration/remote/alerts/alerts.go +++ b/enumeration/remote/alerts/alerts.go @@ -2,10 +2,12 @@ package alerts import ( "fmt" + "strings" "github.com/snyk/driftctl/enumeration/alerter" "github.com/snyk/driftctl/enumeration/remote/common" remoteerror "github.com/snyk/driftctl/enumeration/remote/error" + "github.com/snyk/driftctl/enumeration/resource" "github.com/sirupsen/logrus" ) @@ -21,6 +23,7 @@ type RemoteAccessDeniedAlert struct { message string provider string scanningPhase ScanningPhase + resource *resource.Resource } func NewRemoteAccessDeniedAlert(provider string, scanErr *remoteerror.ResourceScanningError, scanningPhase ScanningPhase) *RemoteAccessDeniedAlert { @@ -28,26 +31,36 @@ func NewRemoteAccessDeniedAlert(provider string, scanErr *remoteerror.ResourceSc switch scanningPhase { case EnumerationPhase: message = fmt.Sprintf( - "Ignoring %s from drift calculation: Listing %s is forbidden: %s", + "An error occured listing %s: listing %s is forbidden: %s", scanErr.Resource(), scanErr.ListedTypeError(), scanErr.RootCause().Error(), ) case DetailsFetchingPhase: message = fmt.Sprintf( - "Ignoring %s from drift calculation: Reading details of %s is forbidden: %s", + "An error occured listing %s: reading details of %s is forbidden: %s", scanErr.Resource(), scanErr.ListedTypeError(), scanErr.RootCause().Error(), ) default: message = fmt.Sprintf( - "Ignoring %s from drift calculation: %s", + "An error occured listing %s: %s", scanErr.Resource(), scanErr.RootCause().Error(), ) } - return &RemoteAccessDeniedAlert{message, provider, scanningPhase} + + var relatedResource *resource.Resource + resourceFQDNSSplit := strings.SplitN(scanErr.Resource(), ".", 2) + if len(resourceFQDNSSplit) == 2 { + relatedResource = &resource.Resource{ + Id: resourceFQDNSSplit[1], + Type: resourceFQDNSSplit[0], + } + } + + return &RemoteAccessDeniedAlert{message, provider, scanningPhase, relatedResource} } func (e *RemoteAccessDeniedAlert) Message() string { @@ -58,6 +71,10 @@ func (e *RemoteAccessDeniedAlert) ShouldIgnoreResource() bool { return true } +func (e *RemoteAccessDeniedAlert) Resource() *resource.Resource { + return e.resource +} + func (e *RemoteAccessDeniedAlert) GetProviderMessage() string { var message string if e.scanningPhase == DetailsFetchingPhase { diff --git a/enumeration/remote/aws/sns_topic_subscription_enumerator.go b/enumeration/remote/aws/sns_topic_subscription_enumerator.go index 8fdd3c4f6..ee64eae71 100644 --- a/enumeration/remote/aws/sns_topic_subscription_enumerator.go +++ b/enumeration/remote/aws/sns_topic_subscription_enumerator.go @@ -33,6 +33,10 @@ func (p *wrongArnTopicAlert) ShouldIgnoreResource() bool { return false } +func (p *wrongArnTopicAlert) Resource() *resource.Resource { + return nil +} + type SNSTopicSubscriptionEnumerator struct { repository repository.SNSRepository factory resource.ResourceFactory diff --git a/pkg/analyser/analyzer.go b/pkg/analyser/analyzer.go index d26f45763..fae7a2615 100644 --- a/pkg/analyser/analyzer.go +++ b/pkg/analyser/analyzer.go @@ -23,6 +23,10 @@ func (u *UnmanagedSecurityGroupRulesAlert) ShouldIgnoreResource() bool { return false } +func (u *UnmanagedSecurityGroupRulesAlert) Resource() *resource.Resource { + return nil +} + type ComputedDiffAlert struct{} func NewComputedDiffAlert() *ComputedDiffAlert { @@ -37,6 +41,10 @@ func (c *ComputedDiffAlert) ShouldIgnoreResource() bool { return false } +func (c *ComputedDiffAlert) Resource() *resource.Resource { + return nil +} + type AnalyzerOptions struct { Deep bool `json:"deep"` OnlyManaged bool `json:"only_managed"` diff --git a/pkg/cmd/scan/output/testdata/output.html b/pkg/cmd/scan/output/testdata/output.html index 9ff8407de..fa0d63d16 100644 --- a/pkg/cmd/scan/output/testdata/output.html +++ b/pkg/cmd/scan/output/testdata/output.html @@ -681,17 +681,17 @@

Jun 10, 2021

  • - Ignoring aws_vpc from drift calculation: Listing aws_vpc is forbidden: dummy error + An error occured listing aws_vpc: listing aws_vpc is forbidden: dummy error
  • - Ignoring aws_sqs from drift calculation: Listing aws_sqs is forbidden: dummy error + An error occured listing aws_sqs: listing aws_sqs is forbidden: dummy error
  • - Ignoring aws_sns from drift calculation: Listing aws_sns is forbidden: dummy error + An error occured listing aws_sns: listing aws_sns is forbidden: dummy error
  • diff --git a/pkg/cmd/scan/output/testdata/output_access_denied_alert_aws.json b/pkg/cmd/scan/output/testdata/output_access_denied_alert_aws.json index aabd2c380..08d997ef4 100644 --- a/pkg/cmd/scan/output/testdata/output_access_denied_alert_aws.json +++ b/pkg/cmd/scan/output/testdata/output_access_denied_alert_aws.json @@ -20,13 +20,13 @@ "alerts": { "": [ { - "message": "Ignoring aws_vpc from drift calculation: Listing aws_vpc is forbidden: dummy error" + "message": "An error occured listing aws_vpc: listing aws_vpc is forbidden: dummy error" }, { - "message": "Ignoring aws_sqs from drift calculation: Listing aws_sqs is forbidden: dummy error" + "message": "An error occured listing aws_sqs: listing aws_sqs is forbidden: dummy error" }, { - "message": "Ignoring aws_sns from drift calculation: Listing aws_sns is forbidden: dummy error" + "message": "An error occured listing aws_sns: listing aws_sns is forbidden: dummy error" } ] }, diff --git a/pkg/cmd/scan/output/testdata/output_access_denied_alert_aws.txt b/pkg/cmd/scan/output/testdata/output_access_denied_alert_aws.txt index 565635f4c..849dae899 100644 --- a/pkg/cmd/scan/output/testdata/output_access_denied_alert_aws.txt +++ b/pkg/cmd/scan/output/testdata/output_access_denied_alert_aws.txt @@ -1,9 +1,9 @@ Found 0 resource(s) - 0% coverage Congrats! Your infrastructure is fully in sync. -Ignoring aws_vpc from drift calculation: Listing aws_vpc is forbidden: dummy error -Ignoring aws_sqs from drift calculation: Listing aws_sqs is forbidden: dummy error -Ignoring aws_sns from drift calculation: Listing aws_sns is forbidden: dummy error +An error occured listing aws_vpc: listing aws_vpc is forbidden: dummy error +An error occured listing aws_sqs: listing aws_sqs is forbidden: dummy error +An error occured listing aws_sns: listing aws_sns is forbidden: dummy error It seems that we got access denied exceptions while listing resources. The latest minimal read-only IAM policy for driftctl is always available here, please update yours: https://docs.driftctl.com/aws/policy diff --git a/pkg/cmd/scan/output/testdata/output_access_denied_alert_github.json b/pkg/cmd/scan/output/testdata/output_access_denied_alert_github.json index efd41e8da..4ae60e698 100644 --- a/pkg/cmd/scan/output/testdata/output_access_denied_alert_github.json +++ b/pkg/cmd/scan/output/testdata/output_access_denied_alert_github.json @@ -20,10 +20,10 @@ "alerts": { "": [ { - "message": "Ignoring github_team from drift calculation: Listing github_team is forbidden: dummy error" + "message": "An error occured listing github_team: listing github_team is forbidden: dummy error" }, { - "message": "Ignoring github_team_membership from drift calculation: Listing github_team is forbidden: dummy error" + "message": "An error occured listing github_team_membership: listing github_team is forbidden: dummy error" } ] }, diff --git a/pkg/cmd/scan/output/testdata/output_access_denied_alert_github.txt b/pkg/cmd/scan/output/testdata/output_access_denied_alert_github.txt index fcfb77628..4d70d5b6a 100644 --- a/pkg/cmd/scan/output/testdata/output_access_denied_alert_github.txt +++ b/pkg/cmd/scan/output/testdata/output_access_denied_alert_github.txt @@ -1,8 +1,8 @@ Found 0 resource(s) - 0% coverage Congrats! Your infrastructure is fully in sync. -Ignoring github_team from drift calculation: Listing github_team is forbidden: dummy error -Ignoring github_team_membership from drift calculation: Listing github_team is forbidden: dummy error +An error occured listing github_team: listing github_team is forbidden: dummy error +An error occured listing github_team_membership: listing github_team is forbidden: dummy error It seems that we got access denied exceptions while listing resources. Please be sure that your Github token has the right permissions, check the last up-to-date documentation there: https://docs.driftctl.com/github/policy diff --git a/pkg/iac/terraform/state/alerts.go b/pkg/iac/terraform/state/alerts.go index c23f9135b..e50b56971 100644 --- a/pkg/iac/terraform/state/alerts.go +++ b/pkg/iac/terraform/state/alerts.go @@ -1,6 +1,10 @@ package state -import "fmt" +import ( + "fmt" + + "github.com/snyk/driftctl/enumeration/resource" +) type StateReadingAlert struct { key string @@ -18,3 +22,7 @@ func (s *StateReadingAlert) Message() string { func (s *StateReadingAlert) ShouldIgnoreResource() bool { return false } + +func (s *StateReadingAlert) Resource() *resource.Resource { + return nil +}