From 44e4fd62f8f5c0d0118d9ec2bdaded8b4e901edf Mon Sep 17 00:00:00 2001 From: Olaf Hartong <8149899+olafhartong@users.noreply.github.com> Date: Sat, 9 Mar 2024 20:55:45 +0100 Subject: [PATCH] V1.3 (#9) * adding limacharlie output * refinement * Added clean skip if secret or password is missing for the input processor * Added ADX batchsize configurability and ElasticCloud querying * updated template to demonstrate ADX batching * typo fix to ADX schema * added path creation and date-variable to csv * added options for 2x date-variable to csv * ignore report folder in git * added markdown table output per action * updated gitignore * updated README to add supported files * update to Markdown output * module updates * updates to docs * huge speed boost by altering write process * year update * sample report update * version bump * added msgraphapi SDK support and initial actions * improvement to array output in fields * adding dynamic groups * refined error reporting * additional stats event * disable by default due to req and speed * change to array to accommodate maintenance * adding auth device id * add log based MFA updates and new edges * default off * version bumps etc * cypher refinement to win performance * improvement to path parsing for config * more MFA based rules * update to gitignore * update to gitignore * added mfa reports * added mfa queries and add them to paths * app consent and roles * refined queries to make BHCE compatible * new actions * added max path lenght to improve performance * added support for user and azuser * added multi tenant support * removed experimental reference * small kql optimization --- .gitignore | 5 +- LICENSE | 2 +- README.md | 33 ++ actions-template.yml | 8 + .../sen_get_az_role_assignments.yml | 2 +- .../01-Sentinel/sen_get_user_mfa_updates.yml | 53 ++ actions/02-MDE/mde_exploitable_hosts.yml | 4 +- actions/02-MDE/mde_new_sessions.yml | 8 +- .../graph_eligible_role_assignments.yml | 2 +- actions/04-MSgraph/msgraph_dynamicgroups.yml | 37 ++ actions/04-MSgraph/msgraph_mfa.yml | 33 ++ actions/04-MSgraph/msgraph_oauthconsent.yml | 29 + actions/06-Elastic/elk_new_sessions.yml | 26 + .../n4j_0_cln_remove_mfa_device_sharing.yml | 15 + .../n4j_0_cln_remove_mfa_email_sharing.yml | 15 + .../n4j_0_cln_remove_mfa_phone_sharing.yml | 15 + .../n4j_1_edge_add_mfa_authdevice_sharing.yml | 20 + .../n4j_1_edge_add_mfa_email_sharing.yml | 20 + .../n4j_1_edge_add_mfa_phone_sharing.yml | 20 + actions/10-Neo4j/n4j_actionable_stats.yml | 27 + .../10-Neo4j/n4j_ad_user_to_high_value.yml | 3 +- .../n4j_exploitable_device_to_high_value.yml | 10 +- .../n4j_exposed_device_to_high_value.yml | 14 +- ...4j_external_serviceprincipal_high_priv.yml | 28 + .../n4j_highvalue_resource_with_alerts.yml | 26 + actions/10-Neo4j/n4j_mfa_device_sharing.yml | 25 + actions/10-Neo4j/n4j_mfa_email_sharing.yml | 25 + actions/10-Neo4j/n4j_mfa_phone_sharing.yml | 25 + .../n4j_owned_device_to_high_value.yml | 2 +- actions/10-Neo4j/n4j_owned_to_keyvault.yml | 2 +- .../10-Neo4j/n4j_owned_user_to_high_value.yml | 20 +- .../n4j-report-1_domainadmins.yml | 25 + .../n4j-report-MFA_Device_sharing.yml | 23 + .../n4j-report-MFA_Email_sharing.yml | 23 + .../n4j-report-MFA_Phone_sharing.yml | 23 + actions/action_schema.json | 39 +- cmd/getcreds.go | 12 +- docs/FEATURE_IDEAS.md | 6 +- go.mod | 70 ++- go.sum | 533 ++++-------------- input_processor/bloodhound.go | 3 + input_processor/elastic.go | 130 +++++ .../input_cmd/graph_getdynamicgroups.go | 67 +++ input_processor/input_cmd/graph_getmfa.go | 149 +++++ .../input_cmd/graph_getoauthconsent.go | 53 ++ input_processor/logscale.go | 4 + input_processor/mde.go | 4 + input_processor/msgraph.go | 4 + input_processor/msgraphapi.go | 108 ++++ input_processor/neo4j.go | 5 + input_processor/sentinel.go | 4 + input_processor/splunk.go | 4 + internal/credentials.go | 5 + internal/version.go | 2 +- main.go | 32 ++ output_processor/adx.go | 7 + output_processor/csv.go | 24 + output_processor/limacharlie.go | 78 +++ output_processor/markdown.go | 88 +++ 59 files changed, 1590 insertions(+), 489 deletions(-) create mode 100644 actions/01-Sentinel/sen_get_user_mfa_updates.yml create mode 100644 actions/04-MSgraph/msgraph_dynamicgroups.yml create mode 100644 actions/04-MSgraph/msgraph_mfa.yml create mode 100644 actions/04-MSgraph/msgraph_oauthconsent.yml create mode 100644 actions/06-Elastic/elk_new_sessions.yml create mode 100644 actions/10-Neo4j/n4j_0_cln_remove_mfa_device_sharing.yml create mode 100644 actions/10-Neo4j/n4j_0_cln_remove_mfa_email_sharing.yml create mode 100644 actions/10-Neo4j/n4j_0_cln_remove_mfa_phone_sharing.yml create mode 100644 actions/10-Neo4j/n4j_1_edge_add_mfa_authdevice_sharing.yml create mode 100644 actions/10-Neo4j/n4j_1_edge_add_mfa_email_sharing.yml create mode 100644 actions/10-Neo4j/n4j_1_edge_add_mfa_phone_sharing.yml create mode 100644 actions/10-Neo4j/n4j_actionable_stats.yml create mode 100644 actions/10-Neo4j/n4j_external_serviceprincipal_high_priv.yml create mode 100644 actions/10-Neo4j/n4j_highvalue_resource_with_alerts.yml create mode 100644 actions/10-Neo4j/n4j_mfa_device_sharing.yml create mode 100644 actions/10-Neo4j/n4j_mfa_email_sharing.yml create mode 100644 actions/10-Neo4j/n4j_mfa_phone_sharing.yml create mode 100644 actions/11-N4J-Reporting/n4j-report-1_domainadmins.yml create mode 100644 actions/11-N4J-Reporting/n4j-report-MFA_Device_sharing.yml create mode 100644 actions/11-N4J-Reporting/n4j-report-MFA_Email_sharing.yml create mode 100644 actions/11-N4J-Reporting/n4j-report-MFA_Phone_sharing.yml create mode 100644 input_processor/elastic.go create mode 100644 input_processor/input_cmd/graph_getdynamicgroups.go create mode 100644 input_processor/input_cmd/graph_getmfa.go create mode 100644 input_processor/input_cmd/graph_getoauthconsent.go create mode 100644 input_processor/msgraphapi.go create mode 100644 output_processor/limacharlie.go create mode 100644 output_processor/markdown.go diff --git a/.gitignore b/.gitignore index 5a8530c..dee1966 100755 --- a/.gitignore +++ b/.gitignore @@ -7,9 +7,10 @@ output/*.csv bin/* configs/* .vscode/* -actionsTEST/* +client_actions/*/* falconhound dist/* .goreleaser.yaml cache.db -.idea/* \ No newline at end of file +.idea/* +report/*/* \ No newline at end of file diff --git a/LICENSE b/LICENSE index d435fd2..103c910 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2023, FalconForce +Copyright (c) 2024, FalconForce Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/README.md b/README.md index c462d48..2e51034 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,11 @@ Currently, FalconHound supports the following data sources and or targets: - Neo4j - MS Graph API (early stage) - CSV files +- Azure Data Explorer (ADX) - beta +- LogScale +- BloodHound CE and BHE (early stage) +- MarkDown files +- Elastic (early stage) Additional data sources and targets are planned for the future. @@ -177,12 +182,40 @@ Each target has several options that can be configured. Depending on the target, All targets have the `Name` and `Enabled` fields. The `Name` field is used to identify the target. The `Enabled` field is used to enable or disable the target. If this is set to false, the target will be ignored. #### CSV + +CSV supports the {{date}} variable, which will be replaced by the current date in the format `YYYY-MM-DD`. This can be used to create daily reports. +This can be used in a folder or file name (e.g. `path/to/filename-{{date}}.csv`) or in the foldername itself. + ```yaml - Name: CSV Enabled: true Path: path/to/filename.csv ``` +#### Markdown + +Markdown supports the {{date}} variable, which will be replaced by the current date in the format `YYYY-MM-DD`. This can be used to create daily reports. +This can be used in a folder or file name (e.g. `path/to/filename-{{date}}.md`) or in the foldername itself. + +```yaml + - Name: Markdown + Enabled: true + Path: path/to/filename.md +``` + Example output: +```markdown +# Results for query: N4J_REPORT_DomainAdmins + +## Get a list of Domain Admins + +Description: Get a list of Domain Admins. +Date: 2024-02-19 + +| Name | ObjectID | +| --- | --- | +| user@domain.NET | S-1-5-21-1122334455-112233445-1112223334-11223344 | +``` + #### Neo4j The Neo4j target will write the results of the query to a Neo4j database. This output is per line and therefore it requires some additional configuration. diff --git a/actions-template.yml b/actions-template.yml index 3e2b341..06843bd 100644 --- a/actions-template.yml +++ b/actions-template.yml @@ -30,4 +30,12 @@ Targets: # Targets are the platforms that this action will pu DisplayName: MDE Exploitable Machines SearchKey: DeviceName Overwrite: true # Overwrite the watchlist with the query results, when false it will append the results to the watchlist + - Name: ADX + Enabled: true + Table: FalconHound + BatchSize: 1000 # Number of records to push to ADX in one batch, these will show up in the ADX table as 1 row with an array of values + - Name: Markdown + Enabled: true + Path: reports/{{date}}/get_sessions_mde.md + diff --git a/actions/01-Sentinel/sen_get_az_role_assignments.yml b/actions/01-Sentinel/sen_get_az_role_assignments.yml index 34974a0..b51b344 100644 --- a/actions/01-Sentinel/sen_get_az_role_assignments.yml +++ b/actions/01-Sentinel/sen_get_az_role_assignments.yml @@ -11,7 +11,7 @@ Query: | let timeframe = 15m; AuditLogs | where ingestion_time() >= ago(timeframe) - | where OperationName startswith "Add member to role" + | where OperationName =~ "Add member to role" | extend TargetResources=parse_json(TargetResources) | extend ObjectId = TargetResources.[0].id, userPrincipalName=TargetResources.[0].userPrincipalName, modifiedProperties=TargetResources.[0].modifiedProperties | extend RoleObjectId = tostring(modifiedProperties[0].newValue),TenantId=AADTenantId diff --git a/actions/01-Sentinel/sen_get_user_mfa_updates.yml b/actions/01-Sentinel/sen_get_user_mfa_updates.yml new file mode 100644 index 0000000..53644f1 --- /dev/null +++ b/actions/01-Sentinel/sen_get_user_mfa_updates.yml @@ -0,0 +1,53 @@ +Name: User MFA setting updates +ID: SEN_AZ_MFA_Updates +Description: Gets all additive and updates to MFA settings, deletions are not captured +Author: FalconForce +Version: '1.0' +Info: |- +Active: true # Enable to run this action +Debug: false # Enable to see query results in the console +SourcePlatform: Sentinel # Sentinel, Watchlist, Neo4j, CSV, MDE, Graph, Splunk +Query: | + let timeframe = 15m; + AuditLogs + | where Result == "success" + | where OperationName == "Update user" + | extend UserPrincipalName = tostring(TargetResources[0].userPrincipalName) + | extend modifiedProperties = parse_json(TargetResources[0].modifiedProperties) + | mv-expand modifiedProperty = modifiedProperties + | extend displayName = tostring(modifiedProperty.displayName), + oldValue = modifiedProperty.oldValue, + newValue = modifiedProperty.newValue + | project-away modifiedProperties, modifiedProperty + | where displayName startswith "Strong" + | mv-expand newValue + | extend newValues = parse_json(tostring(newValue))[0] + | extend MfaAuthenticatorDeviceName = newValues.DeviceName + | extend MfaDeviceId = newValues.DeviceId + | extend MfaPhoneNumber = newValues.PhoneNumber + | extend AuthenticatorFlavor = newValues.AuthenticatorFlavor + | extend MfaEmailAddress = newValues.Email + | extend DeviceTag = newValues.DeviceTag + | where isnotnull(AuthenticatorFlavor) or isnotnull( MfaPhoneNumber) + | extend MfaDeviceId=case(MfaDeviceId == "00000000-0000-0000-0000-000000000000","",MfaDeviceId) + | extend MfaAuthMethods=case((AuthenticatorFlavor =="Authenticator" and DeviceTag =~ "SoftwareTokenActivated"),"SoftwareOath", + (AuthenticatorFlavor =="Authenticator" and DeviceTag !~ "SoftwareTokenActivated"),"MicrosoftAuthenticator", + (isnotempty(MfaPhoneNumber)),"Phone", + (isnotempty(MfaEmailAddress)),"Email", + "Unknown" ) + | project UserPrincipalName=toupper(UserPrincipalName), UserId=toupper(TargetResources[0].id), MfaAuthenticatorDeviceName,MfaPhoneNumber, MfaAuthMethods, MfaDeviceId=toupper(MfaDeviceId), MfaEmailAddress=toupper(MfaEmailAddress) +Targets: # Targets are the platforms that this action will push to (CSV, Neo4j, Sentinel, Wachlist, Slack, Teams, Splunk, Markdown) + - Name: Neo4j + Enabled: true + Query: | + WITH toUpper($objectid) as objectid, $MfaAuthMethods as MfaAuthMethods, $MfaPhoneNumber as MfaPhoneNumber,$MfaAuthenticatorDeviceName as MfaAuthenticatorDeviceName, toUpper($MfaDeviceId) as MfaDeviceId, $MfaEmailAddress as MfaEmailAddress + MATCH (t:AZUser {objectid: objectid}) + SET t.MfaAuthMethods = (CASE WHEN NOT MfaAuthMethods IN coalesce(t.MfaAuthMethods, []) THEN coalesce(t.MfaAuthMethods, []) + [MfaAuthMethods] ELSE t.MfaAuthMethods END) + SET t.MfaPhoneNumber = MfaPhoneNumber, t.MfaAuthenticatorDeviceName = MfaAuthenticatorDeviceName, t.MfaDeviceId = MfaDeviceId, t.MfaEmailAddress = MfaEmailAddress + Parameters: + objectid: UserId + MfaAuthMethods: MfaAuthMethods + MfaPhoneNumber: MfaPhoneNumber + MfaEmailAddress: MfaEmailAddress + MfaAuthenticatorDeviceName: MfaAuthenticatorDeviceName + MfaDeviceId: MfaDeviceId \ No newline at end of file diff --git a/actions/02-MDE/mde_exploitable_hosts.yml b/actions/02-MDE/mde_exploitable_hosts.yml index 5aa3809..eeecbaa 100644 --- a/actions/02-MDE/mde_exploitable_hosts.yml +++ b/actions/02-MDE/mde_exploitable_hosts.yml @@ -42,4 +42,6 @@ Targets: Overwrite: true - Name: ADX Enabled: false - Table: FalconHound \ No newline at end of file + Table: FalconHound + - Name: LimaCharlie + Enabled: false \ No newline at end of file diff --git a/actions/02-MDE/mde_new_sessions.yml b/actions/02-MDE/mde_new_sessions.yml index 68020a5..32b1eb4 100644 --- a/actions/02-MDE/mde_new_sessions.yml +++ b/actions/02-MDE/mde_new_sessions.yml @@ -18,7 +18,7 @@ Query: | | where LogonType !in ("Network","Service") | where isnotempty(AccountSid) | where not(AccountSid matches regex excludeSid) - | extend ComputerName = toupper(DeviceName), DomainName = strcat(tostring(split(DeviceName, '.')[-2]), '.', tostring(split(DeviceName, '.')[-1])) + | extend DeviceName = toupper(DeviceName), DomainName = strcat(tostring(split(DeviceName, '.')[-2]), '.', tostring(split(DeviceName, '.')[-1])) | summarize Timestamp=min(Timestamp) by DeviceName,AccountSid,AccountName,AccountDomain, LogonType # Targets are the platforms that this action will push to (CSV, Neo4j, Sentinel, Wachlist, Slack, Teams, Splunk, Markdown) Targets: @@ -35,8 +35,4 @@ Targets: WatchlistName: FH_MDE_Sessions DisplayName: MDE Sessions SearchKey: AccountName - Overwrite: true - - Name: BHSession - Enabled: false # Experimental - BatchSize: 100 - \ No newline at end of file + Overwrite: true \ No newline at end of file diff --git a/actions/04-MSgraph/graph_eligible_role_assignments.yml b/actions/04-MSgraph/graph_eligible_role_assignments.yml index 0e3efaa..c8494ef 100644 --- a/actions/04-MSgraph/graph_eligible_role_assignments.yml +++ b/actions/04-MSgraph/graph_eligible_role_assignments.yml @@ -5,7 +5,7 @@ Author: FalconForce Version: '1.0' Info: |- Active: true # Enable to run this action -Debug: true # Enable to see query results in the console +Debug: false # Enable to see query results in the console SourcePlatform: MSGraph # Sentinel, Watchlist, Neo4j, MDE, Graph, Splunk Query: | /beta/roleManagement/directory/roleEligibilitySchedules diff --git a/actions/04-MSgraph/msgraph_dynamicgroups.yml b/actions/04-MSgraph/msgraph_dynamicgroups.yml new file mode 100644 index 0000000..b57fda5 --- /dev/null +++ b/actions/04-MSgraph/msgraph_dynamicgroups.yml @@ -0,0 +1,37 @@ +Name: Get Dynamic Groups +ID: GRAPH_DynamicGroups +Description: Get Dynamic Groups and their rules. Requires Directory.Read.All permissions +Author: FalconForce +Version: '1.0' +Info: |- +Active: true # Enable to run this action +Debug: true # Enable to see query results in the console +SourcePlatform: MSGraphApi # Sentinel, Watchlist, Neo4j, MDE, Graph, Splunk +Query: | + GetDynamicGroups +Targets: # Targets are the platforms that this action will push to (CSV, Neo4j, Sentinel, Wachlist, Slack, Teams, Splunk, Markdown) + - Name: Neo4j + Enabled: true + Query: | + WITH toUpper($objectid) as objectid, toUpper($displayname) as name, $membershiprule as membershiprule, $membershiprulestate as membershiprulestate, $grouptype as grouptype, $displayname as displayname, $tenantid as tenantid + MERGE (x:AZBase {objectid:objectid}) + SET x:AZGroup, x+={ + name: name, + tenantid: tenantid, + objectid: objectid, + displayname: displayname, + falconhound:True, + memebershiprule: membershiprule, + membershiprulestate: membershiprulestate, + grouptype: grouptype + } + Parameters: + objectid: ObjectId + grouptype: GroupType + membershiprule: MembershipRule + membershiprulestate: MembershipRuleProcessingState + displayname: DisplayName + tenantid: TenantId + - Name: Markdown + Enabled: false + Path: report/TEST/oauth.md \ No newline at end of file diff --git a/actions/04-MSgraph/msgraph_mfa.yml b/actions/04-MSgraph/msgraph_mfa.yml new file mode 100644 index 0000000..3cfac89 --- /dev/null +++ b/actions/04-MSgraph/msgraph_mfa.yml @@ -0,0 +1,33 @@ +Name: Get MFA Status +ID: GRAPH_MFA_Status +Description: Get per user MFA settings. WARNING, this will be slow. Requires User.Read.All and UserAuthenticationMethod.Read.All permissions +Author: FalconForce +Version: '1.0' +Info: |- +Active: false # disabled by default due to the long processing time, enable only when needed, updates can be gathered from logs +Debug: false # Enable to see query results in the console +SourcePlatform: MSGraphApi # Sentinel, Watchlist, Neo4j, MDE, Graph, Splunk +Query: | + GetMFA +Targets: # Targets are the platforms that this action will push to (CSV, Neo4j, Sentinel, Wachlist, Slack, Teams, Splunk, Markdown) + - Name: Neo4j + Enabled: true + Query: | + WITH toUpper($objectid) as objectid, $MfaAuthMethods as MfaAuthMethods, $MfaPhoneNumber as MfaPhoneNumber, $MfaSmsMethod as MfaSmsMethod, $MfaSignInPreference as MfaSignInPreference, toUpper($MfaEmailAddress) as MfaEmailAddress, $MfaHelloDevice as MfaHelloDevice, $MfaFidoDeviceName as MfaFidoDeviceName, $MfaFidoModel as MfaFidoModel, $MfaAuthenticatorDeviceName as MfaAuthenticatorDeviceName, $MfaAuthenticatorDeviceId as MfaAuthenticatorDeviceId + MATCH (t {objectid: objectid}) + SET t.MfaAuthMethods = MfaAuthMethods, t.MfaPhoneNumber = MfaPhoneNumber, t.MfaSmsMethod = MfaSmsMethod, t.MfaSignInPreference = MfaSignInPreference, t.MfaEmailAddress = MfaEmailAddress, t.MfaHelloDevice = MfaHelloDevice, t.MfaFidoDeviceName = MfaFidoDeviceName, t.MfaFidoModel = MfaFidoModel, t.MfaAuthenticatorDeviceName = MfaAuthenticatorDeviceName, t.MfaAuthenticatorDeviceId = MfaAuthenticatorDeviceId + Parameters: + objectid: ObjectId + MfaAuthMethods: MfaAuthMethods + MfaPhoneNumber: PhoneNumber + MfaSmsMethod: SmsSignInState + MfaSignInPreference: SignInPreference + MfaEmailAddress: MfaEmailAddress + MfaHelloDevice: HelloDevice + MfaFidoDeviceName: FidoDeviceName + MfaFidoModel: FidoModel + MfaAuthenticatorDeviceName: AuthenticatorDeviceName + MfaAuthenticatorDeviceId: AuthenticatorDeviceId + - Name: Markdown + Enabled: false + Path: report/TEST/MFA.md \ No newline at end of file diff --git a/actions/04-MSgraph/msgraph_oauthconsent.yml b/actions/04-MSgraph/msgraph_oauthconsent.yml new file mode 100644 index 0000000..36a8095 --- /dev/null +++ b/actions/04-MSgraph/msgraph_oauthconsent.yml @@ -0,0 +1,29 @@ +Name: Get OAuth Consent +ID: GRAPH_OAuthConsent +Description: Get OAuth Consent. Requires Directory.Read.All permissions +Author: FalconForce +Version: '1.0' +Info: |- +Active: true # Enable to run this action +Debug: false # Enable to see query results in the console +SourcePlatform: MSGraphApi # Sentinel, Watchlist, Neo4j, MDE, Graph, Splunk +Query: | + GetOAuthConsent +Targets: # Targets are the platforms that this action will push to (CSV, Neo4j, Sentinel, Wachlist, Slack, Teams, Splunk, Markdown) + - Name: Neo4j + Enabled: true + Query: | + WITH toUpper($objectid) as objectid, $ConsentType as ConsentType, $StartTime as StartTime, $ExpiryTime as ExpiryTime, toUpper($ResourceId) as ResourceId, $Scope as Scope + MATCH (s {objectid: objectid}) MATCH (t {objectid: ResourceId}) + MERGE (s)-[r:HasConsent]->(t) + SET r.ConsentType = ConsentType, r.StartTime = StartTime, r.ExpiryTime = ExpiryTime, r.Scope = Scope + Parameters: + objectid: ClientId + ConsentType: ConsentType + StartTime: StartTime + ExpiryTime: ExpiryTime + ResourceId: ResourceId + Scope: Scope + - Name: Markdown + Enabled: false + Path: report/TEST/oauth.md \ No newline at end of file diff --git a/actions/06-Elastic/elk_new_sessions.yml b/actions/06-Elastic/elk_new_sessions.yml new file mode 100644 index 0000000..1f5fad5 --- /dev/null +++ b/actions/06-Elastic/elk_new_sessions.yml @@ -0,0 +1,26 @@ +Name: Get new logon sessions +ID: ELK_New_Sessions +Description: Gets all logon events from Elastic Cloud and sends them to Neo4j +Author: FalconForce +Version: '1.0' +Info: | + Gets all logon events from Elastic Cloud, filters out non-user logons, and creates a relationship between the computer and the user in Neo4j, + with the timestamp of the first logon event. +Active: true # Enable to run this action +Debug: false # Enable to see query results in the console +SourcePlatform: Elastic # Sentinel, Watchlist, Neo4j, CSV, MDE, Graph, Splunk +Query: | # Splunk index can be hardcoded or a variable set in the config.yml file + @timestamp:[now-15h TO now] + AND winlog.event_id: 4624 + AND winlog.event_data.TargetUserSid: *S-1-5-21-* + AND winlog.event_data.LogonType: (2 OR 10 OR 11 OR 12 OR 13 OR 14 OR 15) +Targets: # Targets are the platforms that this action will push to (CSV, Neo4j, Sentinel, Wachlist, Slack, Teams, Splunk, Markdown) + - Name: Neo4j + Enabled: true + Query: | + WITH toUpper($Computer) as Computer, toUpper($TargetUserSid) as TargetUserSid, $Timestamp as Timestamp + MATCH (x:Computer {name:Computer}) MATCH (y:User {objectid:TargetUserSid}) MERGE (x)-[r:HasSession]->(y) SET r.since=Timestamp SET r.source='falconhound' + Parameters: + Computer: winlog.computer_name + TargetUserSid: winlog.event_data.TargetUserSid + Timestamp: "@timestamp" \ No newline at end of file diff --git a/actions/10-Neo4j/n4j_0_cln_remove_mfa_device_sharing.yml b/actions/10-Neo4j/n4j_0_cln_remove_mfa_device_sharing.yml new file mode 100644 index 0000000..18f5820 --- /dev/null +++ b/actions/10-Neo4j/n4j_0_cln_remove_mfa_device_sharing.yml @@ -0,0 +1,15 @@ +Name: CLEANUP - Remove the MFA Device Sharing edge +ID: N4J_CLN_Remove_MFA_Device_Sharing +Description: Removes the MFA Device Sharing edge from the graph if the nodes do not share the same device +Author: FalconForce +Version: '1.0' +Info: |- +Active: true # Enable to run this action +Debug: false # Enable to see query results in the console +SourcePlatform: Neo4j +Query: | + MATCH (u1:AZUser)-[r:MfaDeviceSharing]-(u2:AZUser) + WHERE u1.MfaAuthenticatorDeviceId IS NOT NULL AND u2.MfaAuthenticatorDeviceId IS NOT NULL + AND u1.MfaAuthenticatorDeviceId <> u2.MfaAuthenticatorDeviceId + DELETE r +Targets: [] \ No newline at end of file diff --git a/actions/10-Neo4j/n4j_0_cln_remove_mfa_email_sharing.yml b/actions/10-Neo4j/n4j_0_cln_remove_mfa_email_sharing.yml new file mode 100644 index 0000000..37dedf0 --- /dev/null +++ b/actions/10-Neo4j/n4j_0_cln_remove_mfa_email_sharing.yml @@ -0,0 +1,15 @@ +Name: CLEANUP - Remove the MFA Email Sharing edge +ID: N4J_CLN_Remove_MFA_EMAIL_Sharing +Description: Removes the MFA Email edge from the graph if the nodes do not share the same address +Author: FalconForce +Version: '1.0' +Info: |- +Active: true # Enable to run this action +Debug: false # Enable to see query results in the console +SourcePlatform: Neo4j +Query: | + MATCH (u1:AZUser)-[r:MfaEmailSharing]-(u2:AZUser) + WHERE u1.MfaEmailAddress IS NOT NULL AND u2.MfaEmailAddress IS NOT NULL + AND u1.MfaEmailAddress <> u2.MfaEmailAddress + DELETE r +Targets: [] \ No newline at end of file diff --git a/actions/10-Neo4j/n4j_0_cln_remove_mfa_phone_sharing.yml b/actions/10-Neo4j/n4j_0_cln_remove_mfa_phone_sharing.yml new file mode 100644 index 0000000..4137952 --- /dev/null +++ b/actions/10-Neo4j/n4j_0_cln_remove_mfa_phone_sharing.yml @@ -0,0 +1,15 @@ +Name: CLEANUP - Remove the MFA Phone Sharing edge +ID: N4J_CLN_Remove_MFAPhoneSharing +Description: Removes the MFA Phone Sharing edge from the graph if the nodes do not share the same phone number +Author: FalconForce +Version: '1.0' +Info: |- +Active: true # Enable to run this action +Debug: false # Enable to see query results in the console +SourcePlatform: Neo4j +Query: | + MATCH (u1:AZUser)-[r:MfaPhoneSharing]-(u2:AZUser) + WHERE u1.MfaPhoneNumber IS NOT NULL AND u2.MfaPhoneNumber IS NOT NULL + AND u1.MfaPhoneNumber <> u2.MfaPhoneNumber + DELETE r +Targets: [] \ No newline at end of file diff --git a/actions/10-Neo4j/n4j_1_edge_add_mfa_authdevice_sharing.yml b/actions/10-Neo4j/n4j_1_edge_add_mfa_authdevice_sharing.yml new file mode 100644 index 0000000..e36bb2e --- /dev/null +++ b/actions/10-Neo4j/n4j_1_edge_add_mfa_authdevice_sharing.yml @@ -0,0 +1,20 @@ +Name: EDGE - Add MFA Authenticator Device Sharing edges +ID: N4J_EDGE_ADD_MFAAUTHDEVICE_SHARING +Description: Checks for accounts with the same MFA Authenticator device and adds a MfaDeviceSharing edge. +Author: FalconForce +Version: '1.0' +Info: |- +Active: true # Enable to run this action +Debug: false # Enable to see query results in the console +SourcePlatform: Neo4j +Query: | + MATCH (user1:AZUser) + WHERE user1.MfaAuthenticatorDeviceId IS NOT NULL AND user1.MfaAuthenticatorDeviceId <> "" + WITH user1.MfaAuthenticatorDeviceId AS device, COLLECT(user1) AS users + UNWIND users AS u1 + UNWIND users AS u2 + WITH u1, u2 + WHERE id(u1) < id(u2) + MERGE (u1)-[r:MfaDeviceSharing]-(u2) + SET r.enforced = false +Targets: [] \ No newline at end of file diff --git a/actions/10-Neo4j/n4j_1_edge_add_mfa_email_sharing.yml b/actions/10-Neo4j/n4j_1_edge_add_mfa_email_sharing.yml new file mode 100644 index 0000000..9702cf4 --- /dev/null +++ b/actions/10-Neo4j/n4j_1_edge_add_mfa_email_sharing.yml @@ -0,0 +1,20 @@ +Name: EDGE - Add MFA Email Address Sharing edges +ID: N4J_EDGE_ADD_MFA_EMAIL_SHARING +Description: Checks for accounts with the same MFA email address and adds a MfaEmailSharing edge. +Author: FalconForce +Version: '1.0' +Info: |- +Active: true # Enable to run this action +Debug: false # Enable to see query results in the console +SourcePlatform: Neo4j +Query: | + MATCH (user1:AZUser) + WHERE user1.MfaEmailAddress IS NOT NULL AND user1.MfaEmailAddress <> "" + WITH user1.MfaEmailAddress AS device, COLLECT(user1) AS users + UNWIND users AS u1 + UNWIND users AS u2 + WITH u1, u2 + WHERE id(u1) < id(u2) + MERGE (u1)-[r:MfaEmailSharing]-(u2) + SET r.enforced = false +Targets: [] \ No newline at end of file diff --git a/actions/10-Neo4j/n4j_1_edge_add_mfa_phone_sharing.yml b/actions/10-Neo4j/n4j_1_edge_add_mfa_phone_sharing.yml new file mode 100644 index 0000000..8a936c4 --- /dev/null +++ b/actions/10-Neo4j/n4j_1_edge_add_mfa_phone_sharing.yml @@ -0,0 +1,20 @@ +Name: EDGE - Add MFA Phone Sharing edges +ID: N4J_EDGE_ADD_MFAPHONESHARING +Description: Checks for accounts with the same MFA PhoneNumber and adds a MfaPhoneSharing edge. +Author: FalconForce +Version: '1.0' +Info: |- +Active: true # Enable to run this action +Debug: false # Enable to see query results in the console +SourcePlatform: Neo4j +Query: | + MATCH (user1:AZUser) + WHERE user1.MfaPhoneNumber IS NOT NULL AND user1.MfaPhoneNumber <> "" + WITH user1.MfaPhoneNumber AS phoneNumber, COLLECT(user1) AS users + UNWIND users AS u1 + UNWIND users AS u2 + WITH u1, u2 + WHERE id(u1) < id(u2) + MERGE (u1)-[r:MfaPhoneSharing]-(u2) + SET r.enforced = false +Targets: [] \ No newline at end of file diff --git a/actions/10-Neo4j/n4j_actionable_stats.yml b/actions/10-Neo4j/n4j_actionable_stats.yml new file mode 100644 index 0000000..7c6df24 --- /dev/null +++ b/actions/10-Neo4j/n4j_actionable_stats.yml @@ -0,0 +1,27 @@ +Name: N4J Actionble Stats +ID: N4J_Actionable_Stats +Description: This action gets actionable BloodHound stats. +Author: FalconForce +Version: '1.0' +Info: |- +Active: true # Enable to run this action +Debug: false # Enable to see query results in the console +SourcePlatform: Neo4j +Query: | + MATCH (r:User {owned:true}) RETURN {ownedUsers:COUNT(r)} as Stats + UNION + MATCH (r:Computer {owned:true}) RETURN {ownedComputers:COUNT(r)} as Stats + UNION + MATCH (r:Computer {exposed:true}) RETURN {exposedComputers:COUNT(r)} as Stats + UNION + MATCH (r:Computer {exploitable:true}) RETURN {exploitableComputers:COUNT(r)} as Stats + UNION + MATCH ()-[r:HasSession]->() RETURN {activeSessions:COUNT(r)} as Stats + UNION + MATCH ()-[r:HadSession]->() RETURN {formerSessions:COUNT(r)} as Stats +Targets: + - Name: Sentinel + Enabled: true + - Name: Markdown + Enabled: false + Path: report/ActionableStats-{{date}}.md \ No newline at end of file diff --git a/actions/10-Neo4j/n4j_ad_user_to_high_value.yml b/actions/10-Neo4j/n4j_ad_user_to_high_value.yml index 55cd867..41526fc 100644 --- a/actions/10-Neo4j/n4j_ad_user_to_high_value.yml +++ b/actions/10-Neo4j/n4j_ad_user_to_high_value.yml @@ -8,7 +8,8 @@ Active: true # Enable to run this action Debug: false # Enable to see query results in the console SourcePlatform: Neo4j Query: | - MATCH (x:Group{highvalue:true}) + MATCH (x:Group) + WHERE (coalesce(x.system_tags,"") CONTAINS "admin_tier_0" or x.highvalue=true) WITH x.objectid as ObjectID, x.name as Name MATCH (y:Group {objectid:ObjectID}) MATCH (u:User) diff --git a/actions/10-Neo4j/n4j_exploitable_device_to_high_value.yml b/actions/10-Neo4j/n4j_exploitable_device_to_high_value.yml index 20ccee2..9c7da22 100644 --- a/actions/10-Neo4j/n4j_exploitable_device_to_high_value.yml +++ b/actions/10-Neo4j/n4j_exploitable_device_to_high_value.yml @@ -8,7 +8,8 @@ Active: true # Enable to run this action Debug: false # Enable to see query results in the console SourcePlatform: Neo4j Query: | - MATCH (x:Group{highvalue:true}) + MATCH (x:Group) + WHERE (coalesce(x.system_tags,"") CONTAINS "admin_tier_0" or x.highvalue=true) WITH x.objectid as ObjectID, x.name as Name MATCH (y:Group {objectid:ObjectID}) MATCH (u:Computer {exploitable:true}) @@ -21,7 +22,12 @@ Targets: Path: output/exploitable_to_highvaluecount.csv - Name: Sentinel BHQuery: | - MATCH a=shortestPath((c:Computer {exploitable:true})-[*..]->({highvalue:TRUE})) RETURN a + MATCH (x:Group) + WHERE (coalesce(x.system_tags,"") CONTAINS "admin_tier_0" or x.highvalue=true) + WITH x.objectid as ObjectID, x.name as Name + MATCH (y:Group {objectid:ObjectID}) + MATCH (u:Computer {exploitable:true}) + MATCH a=shortestPath((u)-[*1..4]->(y)) RETURN a Enabled: true - Name: Watchlist Enabled: true diff --git a/actions/10-Neo4j/n4j_exposed_device_to_high_value.yml b/actions/10-Neo4j/n4j_exposed_device_to_high_value.yml index 20d687e..d0b4bc2 100644 --- a/actions/10-Neo4j/n4j_exposed_device_to_high_value.yml +++ b/actions/10-Neo4j/n4j_exposed_device_to_high_value.yml @@ -8,7 +8,8 @@ Active: true # Enable to run this action Debug: false # Enable to see query results in the console SourcePlatform: Neo4j Query: | - MATCH (x:Group{highvalue:true}) + MATCH (x:Group) + WHERE (coalesce(x.system_tags,"") CONTAINS "admin_tier_0" or x.highvalue=true) WITH x.objectid as ObjectID, x.name as Name MATCH (y:Group {objectid:ObjectID}) MATCH (u:Computer {exposed:true}) @@ -21,7 +22,12 @@ Targets: Path: output/exposed_to_highvaluecount.csv - Name: Sentinel BHQuery: | - MATCH a=shortestPath((c:Computer {exposed:true})-[*..]->({highvalue:TRUE})) RETURN a + MATCH (x:Group) + WHERE (coalesce(x.system_tags,"") CONTAINS "admin_tier_0" or x.highvalue=true) + WITH x.objectid as ObjectID, x.name as Name + MATCH (y:Group {objectid:ObjectID}) + MATCH (u:Computer {exposed:true}) + MATCH a=shortestPath((u)-[*1..5]->(y)) RETURN a Enabled: true - Name: Watchlist Enabled: true @@ -29,3 +35,7 @@ Targets: DisplayName: Exposed Device to HighValue SearchKey: Name Overwrite: true + - Name: ADX + Enabled: false + Table: FalconHound + BatchSize: 1000 \ No newline at end of file diff --git a/actions/10-Neo4j/n4j_external_serviceprincipal_high_priv.yml b/actions/10-Neo4j/n4j_external_serviceprincipal_high_priv.yml new file mode 100644 index 0000000..c75ad86 --- /dev/null +++ b/actions/10-Neo4j/n4j_external_serviceprincipal_high_priv.yml @@ -0,0 +1,28 @@ +Name: External Service Principal with High Privileges +ID: N4J_Azure_EXT_SP_HIGH_PRIV +Description: This action lists all externally owned Service Principals with high privileges in Azure +Author: FalconForce +Version: '1.0' +Info: More information about the potential impact here > https://posts.specterops.io/microsoft-breach-how-can-i-see-this-in-bloodhound-33c92dca4c65 +Active: false # Enable to run this action +Debug: false # Enable to see query results in the console +SourcePlatform: Neo4j +Query: | + MATCH (t:AZTenant) + WITH t.tenantid as TENANTID + MATCH p=(s)-[:AZContributor|AZMGGrantAppRoles|AZPrivilegedRoleAdmin|AZMGAddOwner|AZMGApplication_ReadWrite_All|AZMGDirectory_ReadWrite_All|AZMGRoleManagement_ReadWrite_Directory|AZUserAccessAdministrator|AZOwner|AZMGGrantRole|AZMGAddMember|AZMGAddOwner|AZMGAddSecret|AZHasRole|AZMemberOf|HasConsent|AZResetPassword*1..]->(r:AZRole) + WHERE (coalesce(s.system_tags,"") CONTAINS "admin_tier_0" or s.highvalue=true) + AND NOT toUpper(s.appownerorganizationid) = TENANTID + AND s.appownerorganizationid CONTAINS "-" + RETURN {SPName:s.name,Roles:COLLECT(r.name), Count:COUNT(r.name)} as info +Targets: + - Name: Sentinel + Enabled: true + BHQuery: | + MATCH (t:AZTenant) + WITH t.tenantid as TENANTID + MATCH p=(s)-[:AZContributor|AZMGGrantAppRoles|AZPrivilegedRoleAdmin|AZMGAddOwner|AZMGApplication_ReadWrite_All|AZMGDirectory_ReadWrite_All|AZMGRoleManagement_ReadWrite_Directory|AZUserAccessAdministrator|AZOwner|AZMGGrantRole|AZMGAddMember|AZMGAddOwner|AZMGAddSecret|AZHasRole|AZMemberOf|HasConsent|AZResetPassword*1..]->(r:AZRole) + WHERE (coalesce(s.system_tags,"") CONTAINS "admin_tier_0" or s.highvalue=true) + AND NOT toUpper(s.appownerorganizationid) = TENANTID + AND s.appownerorganizationid CONTAINS "-" + RETURN * \ No newline at end of file diff --git a/actions/10-Neo4j/n4j_highvalue_resource_with_alerts.yml b/actions/10-Neo4j/n4j_highvalue_resource_with_alerts.yml new file mode 100644 index 0000000..8f3a71a --- /dev/null +++ b/actions/10-Neo4j/n4j_highvalue_resource_with_alerts.yml @@ -0,0 +1,26 @@ +Name: High value resource with alerts +ID: N4J_Highvalue_Resource_with_alerts +Description: Searches for TIER0 or high value nodes with alerts +Author: FalconForce +Version: '1.0' +Info: |- +Active: true # Enable to run this action +Debug: false # Enable to see query results in the console +SourcePlatform: Neo4j +Query: | + MATCH (n {owned:true}) + WHERE (coalesce(n.system_tags,"") CONTAINS "admin_tier_0" or n.highvalue=true) + RETURN {Name: n.name, AlertIds: n.alertid, Description:n.description, NodeType:HEAD([label IN LABELS(n) WHERE label <> "base"])} as info +Targets: + - Name: Sentinel + Enabled: true + BHQuery: | + MATCH (n {owned:true}) + WHERE (coalesce(n.system_tags,"") CONTAINS "admin_tier_0" or n.highvalue=true) + RETURN n + - Name: Watchlist + Enabled: true + WatchlistName: FH_HighValue_Resource_with_alerts + DisplayName: HighValue Resource with Alerts + SearchKey: Name + Overwrite: true diff --git a/actions/10-Neo4j/n4j_mfa_device_sharing.yml b/actions/10-Neo4j/n4j_mfa_device_sharing.yml new file mode 100644 index 0000000..58ab51a --- /dev/null +++ b/actions/10-Neo4j/n4j_mfa_device_sharing.yml @@ -0,0 +1,25 @@ +Name: MFA AuthenticatorDevice Sharing +ID: N4J_MFA_DEVICE_SHARING +Description: MFA AuthenticatorDevice Sharing +Author: FalconForce +Version: '1.0' +Info: |- +Active: true # Enable to run this action +Debug: false # Enable to see query results in the console +SourcePlatform: Neo4j +Query: | + MATCH (u:AZUser) + WHERE u.MfaAuthenticatorDeviceId IS NOT NULL AND u.MfaAuthenticatorDeviceId <> '' + WITH u.MfaAuthenticatorDeviceId AS deviceId, u.MfaAuthenticatorDeviceName as deviceName, collect(u.name) AS userNames, COUNT(u) AS UserCount + WHERE UserCount > 1 + RETURN {DeviceId: deviceId, DeviceName: deviceName ,UserNames: userNames, UserCount: UserCount} AS info + ORDER BY UserCount DESC +Targets: + - Name: Sentinel + Enabled: true + - Name: Watchlist + Enabled: true + WatchlistName: FH_MFA_Shared_AuthenticatorDevice_Users + DisplayName: MFA Shared AuthenticatorDevice Users + SearchKey: DeviceId + Overwrite: true \ No newline at end of file diff --git a/actions/10-Neo4j/n4j_mfa_email_sharing.yml b/actions/10-Neo4j/n4j_mfa_email_sharing.yml new file mode 100644 index 0000000..eec130f --- /dev/null +++ b/actions/10-Neo4j/n4j_mfa_email_sharing.yml @@ -0,0 +1,25 @@ +Name: MFA Email Sharing +ID: N4J_MFA_EMAIL_SHARING +Description: MFA Email Sharing +Author: FalconForce +Version: '1.0' +Info: |- +Active: true # Enable to run this action +Debug: false # Enable to see query results in the console +SourcePlatform: Neo4j +Query: | + MATCH (u:AZUser) + WHERE u.MfaEmailAddress IS NOT NULL AND u.MfaEmailAddress <> '' + WITH u.MfaEmailAddress AS emailAddress, collect(u.name) AS userNames, COUNT(u) AS UserCount + WHERE UserCount > 1 + RETURN {EmailAddress: emailAddress, UserNames: userNames, UserCount: UserCount} AS info + ORDER BY UserCount DESC +Targets: + - Name: Sentinel + Enabled: true + - Name: Watchlist + Enabled: true + WatchlistName: FH_MFA_Shared_Email_Users + DisplayName: MFA Shared Email Users + SearchKey: EmailAddress + Overwrite: true diff --git a/actions/10-Neo4j/n4j_mfa_phone_sharing.yml b/actions/10-Neo4j/n4j_mfa_phone_sharing.yml new file mode 100644 index 0000000..acf1c98 --- /dev/null +++ b/actions/10-Neo4j/n4j_mfa_phone_sharing.yml @@ -0,0 +1,25 @@ +Name: MFA Phone Sharing +ID: N4J_MFA_PHONE_SHARING +Description: MFA Phone Sharing +Author: FalconForce +Version: '1.0' +Info: |- +Active: true # Enable to run this action +Debug: false # Enable to see query results in the console +SourcePlatform: Neo4j +Query: | + MATCH (u:AZUser) + WHERE u.MfaPhoneNumber IS NOT NULL AND u.MfaPhoneNumber <> '' + WITH u.MfaPhoneNumber AS phoneNumber, collect(u.name) AS userNames, COUNT(u) AS UserCount + WHERE UserCount > 1 + RETURN {PhoneNumber: phoneNumber, UserNames: userNames, UserCount: UserCount} AS info + ORDER BY UserCount DESC +Targets: + - Name: Sentinel + Enabled: true + - Name: Watchlist + Enabled: true + WatchlistName: FH_MFA_Shared_PhoneNumber_Users + DisplayName: MFA Shared PhoneNumber Users + SearchKey: PhoneNumber + Overwrite: true \ No newline at end of file diff --git a/actions/10-Neo4j/n4j_owned_device_to_high_value.yml b/actions/10-Neo4j/n4j_owned_device_to_high_value.yml index 233986f..fbda854 100644 --- a/actions/10-Neo4j/n4j_owned_device_to_high_value.yml +++ b/actions/10-Neo4j/n4j_owned_device_to_high_value.yml @@ -12,7 +12,7 @@ Query: | WITH x.objectid as ObjectID, x.name as Name MATCH (y:Group {objectid:ObjectID}) MATCH (u:Computer {owned:true}) - WITH Name, COUNT(shortestPath((u)-[:MemberOf|HasSession|AdminTo|AllExtendedRights|AddMember|ForceChangePassword|GenericAll|GenericWrite|Owns|WriteDacl|WriteOwner|CanRDP|ExecuteDCOM|AllowedToDelegate|ReadLAPSPassword|Contains|GPLink|AddAllowedToAct|AllowedToAct|WriteAccountRestrictions|SQLAdmin|ReadGMSAPassword|HasSIDHistory|CanPSRemote|SyncLAPSPassword|DumpSMSAPassword|AZMGGrantRole|AZMGAddSecret|AZMGAddOwner|AZMGAddMember|AZMGGrantAppRoles|AZNodeResourceGroup|AZWebsiteContributor|AZLogicAppContributo|AZAutomationContributor|AZAKSContributor|AZAddMembers|AZAddOwner|AZAddSecret|AZAvereContributor|AZContains|AZContributor|AZExecuteCommand|AZGetCertificates|AZGetKeys|AZGetSecrets|AZGlobalAdmin|AZHasRole|AZManagedIdentity|AZMemberOf|AZOwns|AZPrivilegedAuthAdmin|AZPrivilegedRoleAdmin|AZResetPassword|AZUserAccessAdministrator|AZAppAdmin|AZCloudAppAdmin|AZRunsAs|AZKeyVaultContributor|AZVMAdminLogin|AZVMContributor|AZLogicAppContributor|AddSelf|WriteSPN|AddKeyCredentialLink|DCSync|HadSession]->(y))) as Direct, COUNT(shortestPath((u)-[:MemberOf|HasSession|AdminTo|AllExtendedRights|AddMember|ForceChangePassword|GenericAll|GenericWrite|Owns|WriteDacl|WriteOwner|CanRDP|ExecuteDCOM|AllowedToDelegate|ReadLAPSPassword|Contains|GPLink|AddAllowedToAct|AllowedToAct|WriteAccountRestrictions|SQLAdmin|ReadGMSAPassword|HasSIDHistory|CanPSRemote|SyncLAPSPassword|DumpSMSAPassword|AZMGGrantRole|AZMGAddSecret|AZMGAddOwner|AZMGAddMember|AZMGGrantAppRoles|AZNodeResourceGroup|AZWebsiteContributor|AZLogicAppContributo|AZAutomationContributor|AZAKSContributor|AZAddMembers|AZAddOwner|AZAddSecret|AZAvereContributor|AZContains|AZContributor|AZExecuteCommand|AZGetCertificates|AZGetKeys|AZGetSecrets|AZGlobalAdmin|AZHasRole|AZManagedIdentity|AZMemberOf|AZOwns|AZPrivilegedAuthAdmin|AZPrivilegedRoleAdmin|AZResetPassword|AZUserAccessAdministrator|AZAppAdmin|AZCloudAppAdmin|AZRunsAs|AZKeyVaultContributor|AZVMAdminLogin|AZVMContributor|AZLogicAppContributor|AddSelf|WriteSPN|AddKeyCredentialLink|DCSync|HadSession*1..]->(y))) as Nested, nodes(shortestPath((u)-[:MemberOf|HasSession|AdminTo|AllExtendedRights|AddMember|ForceChangePassword|GenericAll|GenericWrite|Owns|WriteDacl|WriteOwner|CanRDP|ExecuteDCOM|AllowedToDelegate|ReadLAPSPassword|Contains|GPLink|AddAllowedToAct|AllowedToAct|WriteAccountRestrictions|SQLAdmin|ReadGMSAPassword|HasSIDHistory|CanPSRemote|SyncLAPSPassword|DumpSMSAPassword|AZMGGrantRole|AZMGAddSecret|AZMGAddOwner|AZMGAddMember|AZMGGrantAppRoles|AZNodeResourceGroup|AZWebsiteContributor|AZLogicAppContributo|AZAutomationContributor|AZAKSContributor|AZAddMembers|AZAddOwner|AZAddSecret|AZAvereContributor|AZContains|AZContributor|AZExecuteCommand|AZGetCertificates|AZGetKeys|AZGetSecrets|AZGlobalAdmin|AZHasRole|AZManagedIdentity|AZMemberOf|AZOwns|AZPrivilegedAuthAdmin|AZPrivilegedRoleAdmin|AZResetPassword|AZUserAccessAdministrator|AZAppAdmin|AZCloudAppAdmin|AZRunsAs|AZKeyVaultContributor|AZVMAdminLogin|AZVMContributor|AZLogicAppContributor|AddSelf|WriteSPN|AddKeyCredentialLink|DCSync|HadSession]->(y))) as DirectNames, nodes(shortestPath((u)-[:MemberOf|HasSession|AdminTo|AllExtendedRights|AddMember|ForceChangePassword|GenericAll|GenericWrite|Owns|WriteDacl|WriteOwner|CanRDP|ExecuteDCOM|AllowedToDelegate|ReadLAPSPassword|Contains|GPLink|AddAllowedToAct|AllowedToAct|WriteAccountRestrictions|SQLAdmin|ReadGMSAPassword|HasSIDHistory|CanPSRemote|SyncLAPSPassword|DumpSMSAPassword|AZMGGrantRole|AZMGAddSecret|AZMGAddOwner|AZMGAddMember|AZMGGrantAppRoles|AZNodeResourceGroup|AZWebsiteContributor|AZLogicAppContributo|AZAutomationContributor|AZAKSContributor|AZAddMembers|AZAddOwner|AZAddSecret|AZAvereContributor|AZContains|AZContributor|AZExecuteCommand|AZGetCertificates|AZGetKeys|AZGetSecrets|AZGlobalAdmin|AZHasRole|AZManagedIdentity|AZMemberOf|AZOwns|AZPrivilegedAuthAdmin|AZPrivilegedRoleAdmin|AZResetPassword|AZUserAccessAdministrator|AZAppAdmin|AZCloudAppAdmin|AZRunsAs|AZKeyVaultContributor|AZVMAdminLogin|AZVMContributor|AZLogicAppContributor|AddSelf|WriteSPN|AddKeyCredentialLink|DCSync|HadSession*1..]->(y))) as NestedNames + WITH Name, COUNT(shortestPath((u)-[:MfaDeviceSharing|MfaEmailSharing|MfaPhoneNumberSharing|MemberOf|HasSession|AdminTo|AllExtendedRights|AddMember|ForceChangePassword|GenericAll|GenericWrite|Owns|WriteDacl|WriteOwner|CanRDP|ExecuteDCOM|AllowedToDelegate|ReadLAPSPassword|Contains|GPLink|AddAllowedToAct|AllowedToAct|WriteAccountRestrictions|SQLAdmin|ReadGMSAPassword|HasSIDHistory|CanPSRemote|SyncLAPSPassword|DumpSMSAPassword|AZMGGrantRole|AZMGAddSecret|AZMGAddOwner|AZMGAddMember|AZMGGrantAppRoles|AZNodeResourceGroup|AZWebsiteContributor|AZLogicAppContributo|AZAutomationContributor|AZAKSContributor|AZAddMembers|AZAddOwner|AZAddSecret|AZAvereContributor|AZContains|AZContributor|AZExecuteCommand|AZGetCertificates|AZGetKeys|AZGetSecrets|AZGlobalAdmin|AZHasRole|AZManagedIdentity|AZMemberOf|AZOwns|AZPrivilegedAuthAdmin|AZPrivilegedRoleAdmin|AZResetPassword|AZUserAccessAdministrator|AZAppAdmin|AZCloudAppAdmin|AZRunsAs|AZKeyVaultContributor|AZVMAdminLogin|AZVMContributor|AZLogicAppContributor|AddSelf|WriteSPN|AddKeyCredentialLink|DCSync|HadSession]->(y))) as Direct, COUNT(shortestPath((u)-[:MemberOf|HasSession|AdminTo|AllExtendedRights|AddMember|ForceChangePassword|GenericAll|GenericWrite|Owns|WriteDacl|WriteOwner|CanRDP|ExecuteDCOM|AllowedToDelegate|ReadLAPSPassword|Contains|GPLink|AddAllowedToAct|AllowedToAct|WriteAccountRestrictions|SQLAdmin|ReadGMSAPassword|HasSIDHistory|CanPSRemote|SyncLAPSPassword|DumpSMSAPassword|AZMGGrantRole|AZMGAddSecret|AZMGAddOwner|AZMGAddMember|AZMGGrantAppRoles|AZNodeResourceGroup|AZWebsiteContributor|AZLogicAppContributo|AZAutomationContributor|AZAKSContributor|AZAddMembers|AZAddOwner|AZAddSecret|AZAvereContributor|AZContains|AZContributor|AZExecuteCommand|AZGetCertificates|AZGetKeys|AZGetSecrets|AZGlobalAdmin|AZHasRole|AZManagedIdentity|AZMemberOf|AZOwns|AZPrivilegedAuthAdmin|AZPrivilegedRoleAdmin|AZResetPassword|AZUserAccessAdministrator|AZAppAdmin|AZCloudAppAdmin|AZRunsAs|AZKeyVaultContributor|AZVMAdminLogin|AZVMContributor|AZLogicAppContributor|AddSelf|WriteSPN|AddKeyCredentialLink|DCSync|HadSession*1..]->(y))) as Nested, nodes(shortestPath((u)-[:MemberOf|HasSession|AdminTo|AllExtendedRights|AddMember|ForceChangePassword|GenericAll|GenericWrite|Owns|WriteDacl|WriteOwner|CanRDP|ExecuteDCOM|AllowedToDelegate|ReadLAPSPassword|Contains|GPLink|AddAllowedToAct|AllowedToAct|WriteAccountRestrictions|SQLAdmin|ReadGMSAPassword|HasSIDHistory|CanPSRemote|SyncLAPSPassword|DumpSMSAPassword|AZMGGrantRole|AZMGAddSecret|AZMGAddOwner|AZMGAddMember|AZMGGrantAppRoles|AZNodeResourceGroup|AZWebsiteContributor|AZLogicAppContributo|AZAutomationContributor|AZAKSContributor|AZAddMembers|AZAddOwner|AZAddSecret|AZAvereContributor|AZContains|AZContributor|AZExecuteCommand|AZGetCertificates|AZGetKeys|AZGetSecrets|AZGlobalAdmin|AZHasRole|AZManagedIdentity|AZMemberOf|AZOwns|AZPrivilegedAuthAdmin|AZPrivilegedRoleAdmin|AZResetPassword|AZUserAccessAdministrator|AZAppAdmin|AZCloudAppAdmin|AZRunsAs|AZKeyVaultContributor|AZVMAdminLogin|AZVMContributor|AZLogicAppContributor|AddSelf|WriteSPN|AddKeyCredentialLink|DCSync|HadSession]->(y))) as DirectNames, nodes(shortestPath((u)-[:MemberOf|HasSession|AdminTo|AllExtendedRights|AddMember|ForceChangePassword|GenericAll|GenericWrite|Owns|WriteDacl|WriteOwner|CanRDP|ExecuteDCOM|AllowedToDelegate|ReadLAPSPassword|Contains|GPLink|AddAllowedToAct|AllowedToAct|WriteAccountRestrictions|SQLAdmin|ReadGMSAPassword|HasSIDHistory|CanPSRemote|SyncLAPSPassword|DumpSMSAPassword|AZMGGrantRole|AZMGAddSecret|AZMGAddOwner|AZMGAddMember|AZMGGrantAppRoles|AZNodeResourceGroup|AZWebsiteContributor|AZLogicAppContributo|AZAutomationContributor|AZAKSContributor|AZAddMembers|AZAddOwner|AZAddSecret|AZAvereContributor|AZContains|AZContributor|AZExecuteCommand|AZGetCertificates|AZGetKeys|AZGetSecrets|AZGlobalAdmin|AZHasRole|AZManagedIdentity|AZMemberOf|AZOwns|AZPrivilegedAuthAdmin|AZPrivilegedRoleAdmin|AZResetPassword|AZUserAccessAdministrator|AZAppAdmin|AZCloudAppAdmin|AZRunsAs|AZKeyVaultContributor|AZVMAdminLogin|AZVMContributor|AZLogicAppContributor|AddSelf|WriteSPN|AddKeyCredentialLink|DCSync|HadSession*1..]->(y))) as NestedNames WHERE Nested <> 0 or Direct <> 0 RETURN {Name: Name, Direct: Direct, DirectNames: [node in DirectNames | node.name],Nested: Nested, NestedNames: [node in NestedNames | node.name]} as info Targets: diff --git a/actions/10-Neo4j/n4j_owned_to_keyvault.yml b/actions/10-Neo4j/n4j_owned_to_keyvault.yml index d8b1b85..b2bbe26 100644 --- a/actions/10-Neo4j/n4j_owned_to_keyvault.yml +++ b/actions/10-Neo4j/n4j_owned_to_keyvault.yml @@ -8,7 +8,7 @@ Active: true # Enable to run this action Debug: false # Enable to see query results in the console SourcePlatform: Neo4j Query: | - MATCH (u {owned: true})-[r]->(g:AZKeyVault) + MATCH (u {owned: true})-[*1..3]->(g:AZKeyVault) WITH u.Name as Name, COUNT(g.Name) as KeyVaultCount, COLLECT(g.Name) as KeyVaults RETURN {Name: Name, KeyVaultCount: KeyVaultCount, KeyVaults: KeyVaults} as info Targets: diff --git a/actions/10-Neo4j/n4j_owned_user_to_high_value.yml b/actions/10-Neo4j/n4j_owned_user_to_high_value.yml index 058df27..d0d5b40 100644 --- a/actions/10-Neo4j/n4j_owned_user_to_high_value.yml +++ b/actions/10-Neo4j/n4j_owned_user_to_high_value.yml @@ -1,4 +1,4 @@ -Name: AD owned user with a path to high-value nodes +Name: Owned user with a path to high-value nodes ID: N4J_Owned_User_to_HighValue Description: Counts all direct and nested shortest paths to high-value nodes from owned users. Author: FalconForce @@ -8,11 +8,21 @@ Active: true # Enable to run this action Debug: false # Enable to see query results in the console SourcePlatform: Neo4j Query: | - MATCH (x:Group{highvalue:true}) - WITH x.objectid as ObjectID, x.name as Name + MATCH (x:Group) + WHERE (coalesce(x.system_tags,"") CONTAINS "admin_tier_0" OR x.highvalue=true) + WITH x.objectid AS ObjectID, x.name AS Name MATCH (y:Group {objectid:ObjectID}) - MATCH (u:User {owned:true}) - WITH Name, COUNT(shortestPath((u)-[:MemberOf|HasSession|AdminTo|AllExtendedRights|AddMember|ForceChangePassword|GenericAll|GenericWrite|Owns|WriteDacl|WriteOwner|CanRDP|ExecuteDCOM|AllowedToDelegate|ReadLAPSPassword|Contains|GPLink|AddAllowedToAct|AllowedToAct|WriteAccountRestrictions|SQLAdmin|ReadGMSAPassword|HasSIDHistory|CanPSRemote|SyncLAPSPassword|DumpSMSAPassword|AZMGGrantRole|AZMGAddSecret|AZMGAddOwner|AZMGAddMember|AZMGGrantAppRoles|AZNodeResourceGroup|AZWebsiteContributor|AZLogicAppContributo|AZAutomationContributor|AZAKSContributor|AZAddMembers|AZAddOwner|AZAddSecret|AZAvereContributor|AZContains|AZContributor|AZExecuteCommand|AZGetCertificates|AZGetKeys|AZGetSecrets|AZGlobalAdmin|AZHasRole|AZManagedIdentity|AZMemberOf|AZOwns|AZPrivilegedAuthAdmin|AZPrivilegedRoleAdmin|AZResetPassword|AZUserAccessAdministrator|AZAppAdmin|AZCloudAppAdmin|AZRunsAs|AZKeyVaultContributor|AZVMAdminLogin|AZVMContributor|AZLogicAppContributor|AddSelf|WriteSPN|AddKeyCredentialLink|DCSync|HadSession]->(y))) as Direct, COUNT(shortestPath((u)-[:MemberOf|HasSession|AdminTo|AllExtendedRights|AddMember|ForceChangePassword|GenericAll|GenericWrite|Owns|WriteDacl|WriteOwner|CanRDP|ExecuteDCOM|AllowedToDelegate|ReadLAPSPassword|Contains|GPLink|AddAllowedToAct|AllowedToAct|WriteAccountRestrictions|SQLAdmin|ReadGMSAPassword|HasSIDHistory|CanPSRemote|SyncLAPSPassword|DumpSMSAPassword|AZMGGrantRole|AZMGAddSecret|AZMGAddOwner|AZMGAddMember|AZMGGrantAppRoles|AZNodeResourceGroup|AZWebsiteContributor|AZLogicAppContributo|AZAutomationContributor|AZAKSContributor|AZAddMembers|AZAddOwner|AZAddSecret|AZAvereContributor|AZContains|AZContributor|AZExecuteCommand|AZGetCertificates|AZGetKeys|AZGetSecrets|AZGlobalAdmin|AZHasRole|AZManagedIdentity|AZMemberOf|AZOwns|AZPrivilegedAuthAdmin|AZPrivilegedRoleAdmin|AZResetPassword|AZUserAccessAdministrator|AZAppAdmin|AZCloudAppAdmin|AZRunsAs|AZKeyVaultContributor|AZVMAdminLogin|AZVMContributor|AZLogicAppContributor|AddSelf|WriteSPN|AddKeyCredentialLink|DCSync|HadSession*1..]->(y))) as Nested, nodes(shortestPath((u)-[:MemberOf|HasSession|AdminTo|AllExtendedRights|AddMember|ForceChangePassword|GenericAll|GenericWrite|Owns|WriteDacl|WriteOwner|CanRDP|ExecuteDCOM|AllowedToDelegate|ReadLAPSPassword|Contains|GPLink|AddAllowedToAct|AllowedToAct|WriteAccountRestrictions|SQLAdmin|ReadGMSAPassword|HasSIDHistory|CanPSRemote|SyncLAPSPassword|DumpSMSAPassword|AZMGGrantRole|AZMGAddSecret|AZMGAddOwner|AZMGAddMember|AZMGGrantAppRoles|AZNodeResourceGroup|AZWebsiteContributor|AZLogicAppContributo|AZAutomationContributor|AZAKSContributor|AZAddMembers|AZAddOwner|AZAddSecret|AZAvereContributor|AZContains|AZContributor|AZExecuteCommand|AZGetCertificates|AZGetKeys|AZGetSecrets|AZGlobalAdmin|AZHasRole|AZManagedIdentity|AZMemberOf|AZOwns|AZPrivilegedAuthAdmin|AZPrivilegedRoleAdmin|AZResetPassword|AZUserAccessAdministrator|AZAppAdmin|AZCloudAppAdmin|AZRunsAs|AZKeyVaultContributor|AZVMAdminLogin|AZVMContributor|AZLogicAppContributor|AddSelf|WriteSPN|AddKeyCredentialLink|DCSync|HadSession]->(y))) as DirectNames, nodes(shortestPath((u)-[:MemberOf|HasSession|AdminTo|AllExtendedRights|AddMember|ForceChangePassword|GenericAll|GenericWrite|Owns|WriteDacl|WriteOwner|CanRDP|ExecuteDCOM|AllowedToDelegate|ReadLAPSPassword|Contains|GPLink|AddAllowedToAct|AllowedToAct|WriteAccountRestrictions|SQLAdmin|ReadGMSAPassword|HasSIDHistory|CanPSRemote|SyncLAPSPassword|DumpSMSAPassword|AZMGGrantRole|AZMGAddSecret|AZMGAddOwner|AZMGAddMember|AZMGGrantAppRoles|AZNodeResourceGroup|AZWebsiteContributor|AZLogicAppContributo|AZAutomationContributor|AZAKSContributor|AZAddMembers|AZAddOwner|AZAddSecret|AZAvereContributor|AZContains|AZContributor|AZExecuteCommand|AZGetCertificates|AZGetKeys|AZGetSecrets|AZGlobalAdmin|AZHasRole|AZManagedIdentity|AZMemberOf|AZOwns|AZPrivilegedAuthAdmin|AZPrivilegedRoleAdmin|AZResetPassword|AZUserAccessAdministrator|AZAppAdmin|AZCloudAppAdmin|AZRunsAs|AZKeyVaultContributor|AZVMAdminLogin|AZVMContributor|AZLogicAppContributor|AddSelf|WriteSPN|AddKeyCredentialLink|DCSync|HadSession*1..]->(y))) as NestedNames + WITH Name, y + // Match Users with owned:true + OPTIONAL MATCH (u:User {owned:true}) + WITH Name, y, u + WHERE u IS NOT NULL + WITH Name, y, COLLECT(u) AS Users + // Match AZUsers with owned:true + OPTIONAL MATCH (u:AZUser {owned:true}) + WITH Name, y, Users + COLLECT(u) AS AllUsers + UNWIND AllUsers AS AllU + WITH Name, COUNT(shortestPath((AllU)-[:MfaDeviceSharing|MfaEmailSharing|MfaPhoneNumberSharing|MemberOf|HasSession|AdminTo|AllExtendedRights|AddMember|ForceChangePassword|GenericAll|GenericWrite|Owns|WriteDacl|WriteOwner|CanRDP|ExecuteDCOM|AllowedToDelegate|ReadLAPSPassword|Contains|GPLink|AddAllowedToAct|AllowedToAct|WriteAccountRestrictions|SQLAdmin|ReadGMSAPassword|HasSIDHistory|CanPSRemote|SyncLAPSPassword|DumpSMSAPassword|AZMGGrantRole|AZMGAddSecret|AZMGAddOwner|AZMGAddMember|AZMGGrantAppRoles|AZNodeResourceGroup|AZWebsiteContributor|AZLogicAppContributo|AZAutomationContributor|AZAKSContributor|AZAddMembers|AZAddOwner|AZAddSecret|AZAvereContributor|AZContains|AZContributor|AZExecuteCommand|AZGetCertificates|AZGetKeys|AZGetSecrets|AZGlobalAdmin|AZHasRole|AZManagedIdentity|AZMemberOf|AZOwns|AZPrivilegedAuthAdmin|AZPrivilegedRoleAdmin|AZResetPassword|AZUserAccessAdministrator|AZAppAdmin|AZCloudAppAdmin|AZRunsAs|AZKeyVaultContributor|AZVMAdminLogin|AZVMContributor|AZLogicAppContributor|AddSelf|WriteSPN|AddKeyCredentialLink|DCSync|HadSession]->(y))) as Direct, COUNT(shortestPath((AllU)-[:MemberOf|HasSession|AdminTo|AllExtendedRights|AddMember|ForceChangePassword|GenericAll|GenericWrite|Owns|WriteDacl|WriteOwner|CanRDP|ExecuteDCOM|AllowedToDelegate|ReadLAPSPassword|Contains|GPLink|AddAllowedToAct|AllowedToAct|WriteAccountRestrictions|SQLAdmin|ReadGMSAPassword|HasSIDHistory|CanPSRemote|SyncLAPSPassword|DumpSMSAPassword|AZMGGrantRole|AZMGAddSecret|AZMGAddOwner|AZMGAddMember|AZMGGrantAppRoles|AZNodeResourceGroup|AZWebsiteContributor|AZLogicAppContributo|AZAutomationContributor|AZAKSContributor|AZAddMembers|AZAddOwner|AZAddSecret|AZAvereContributor|AZContains|AZContributor|AZExecuteCommand|AZGetCertificates|AZGetKeys|AZGetSecrets|AZGlobalAdmin|AZHasRole|AZManagedIdentity|AZMemberOf|AZOwns|AZPrivilegedAuthAdmin|AZPrivilegedRoleAdmin|AZResetPassword|AZUserAccessAdministrator|AZAppAdmin|AZCloudAppAdmin|AZRunsAs|AZKeyVaultContributor|AZVMAdminLogin|AZVMContributor|AZLogicAppContributor|AddSelf|WriteSPN|AddKeyCredentialLink|DCSync|HadSession*1..]->(y))) as Nested, nodes(shortestPath((AllU)-[:MemberOf|HasSession|AdminTo|AllExtendedRights|AddMember|ForceChangePassword|GenericAll|GenericWrite|Owns|WriteDacl|WriteOwner|CanRDP|ExecuteDCOM|AllowedToDelegate|ReadLAPSPassword|Contains|GPLink|AddAllowedToAct|AllowedToAct|WriteAccountRestrictions|SQLAdmin|ReadGMSAPassword|HasSIDHistory|CanPSRemote|SyncLAPSPassword|DumpSMSAPassword|AZMGGrantRole|AZMGAddSecret|AZMGAddOwner|AZMGAddMember|AZMGGrantAppRoles|AZNodeResourceGroup|AZWebsiteContributor|AZLogicAppContributo|AZAutomationContributor|AZAKSContributor|AZAddMembers|AZAddOwner|AZAddSecret|AZAvereContributor|AZContains|AZContributor|AZExecuteCommand|AZGetCertificates|AZGetKeys|AZGetSecrets|AZGlobalAdmin|AZHasRole|AZManagedIdentity|AZMemberOf|AZOwns|AZPrivilegedAuthAdmin|AZPrivilegedRoleAdmin|AZResetPassword|AZUserAccessAdministrator|AZAppAdmin|AZCloudAppAdmin|AZRunsAs|AZKeyVaultContributor|AZVMAdminLogin|AZVMContributor|AZLogicAppContributor|AddSelf|WriteSPN|AddKeyCredentialLink|DCSync|HadSession]->(y))) as DirectNames, nodes(shortestPath((AllU)-[:MemberOf|HasSession|AdminTo|AllExtendedRights|AddMember|ForceChangePassword|GenericAll|GenericWrite|Owns|WriteDacl|WriteOwner|CanRDP|ExecuteDCOM|AllowedToDelegate|ReadLAPSPassword|Contains|GPLink|AddAllowedToAct|AllowedToAct|WriteAccountRestrictions|SQLAdmin|ReadGMSAPassword|HasSIDHistory|CanPSRemote|SyncLAPSPassword|DumpSMSAPassword|AZMGGrantRole|AZMGAddSecret|AZMGAddOwner|AZMGAddMember|AZMGGrantAppRoles|AZNodeResourceGroup|AZWebsiteContributor|AZLogicAppContributo|AZAutomationContributor|AZAKSContributor|AZAddMembers|AZAddOwner|AZAddSecret|AZAvereContributor|AZContains|AZContributor|AZExecuteCommand|AZGetCertificates|AZGetKeys|AZGetSecrets|AZGlobalAdmin|AZHasRole|AZManagedIdentity|AZMemberOf|AZOwns|AZPrivilegedAuthAdmin|AZPrivilegedRoleAdmin|AZResetPassword|AZUserAccessAdministrator|AZAppAdmin|AZCloudAppAdmin|AZRunsAs|AZKeyVaultContributor|AZVMAdminLogin|AZVMContributor|AZLogicAppContributor|AddSelf|WriteSPN|AddKeyCredentialLink|DCSync|HadSession*1..]->(y))) as NestedNames WHERE Nested <> 0 or Direct <> 0 RETURN {Name: Name, Direct: Direct, DirectNames: [node in DirectNames | node.name],Nested: Nested, NestedNames: [node in NestedNames | node.name]} as info Targets: diff --git a/actions/11-N4J-Reporting/n4j-report-1_domainadmins.yml b/actions/11-N4J-Reporting/n4j-report-1_domainadmins.yml new file mode 100644 index 0000000..c5a7e98 --- /dev/null +++ b/actions/11-N4J-Reporting/n4j-report-1_domainadmins.yml @@ -0,0 +1,25 @@ +Name: Get a list of Domain Admins +ID: N4J_REPORT_DomainAdmins +Description: Get a list of Domain Admins. +Author: FalconForce +Version: '1.0' +Info: |- +Active: true # Enable to run this action +Debug: false # Enable to see query results in the console +SourcePlatform: Neo4j +Query: | + MATCH (n:Group) + WHERE n.objectid =~ '(?i)S-1-5.*-512' + WITH n MATCH (n)<-[r:MemberOf*1..]-(m) + RETURN {Name: m.name , ObjectID: m.objectid} as info +Targets: + - Name: CSV + Enabled: true + Path: report/{{date}}/BH-DA.1_pathToDA_{{date}}.csv + - Name: ADX + Enabled: false + Table: FalconHound + BatchSize: 1000 + - Name: Markdown + Enabled: true + Path: report/{{date}}/BH-DA.1_pathToDA_{{date}}.md \ No newline at end of file diff --git a/actions/11-N4J-Reporting/n4j-report-MFA_Device_sharing.yml b/actions/11-N4J-Reporting/n4j-report-MFA_Device_sharing.yml new file mode 100644 index 0000000..2c19e71 --- /dev/null +++ b/actions/11-N4J-Reporting/n4j-report-MFA_Device_sharing.yml @@ -0,0 +1,23 @@ +Name: MFA Device Sharing +ID: N4J_REPORT_MFA_DEVICE_SHARING +Description: MFA Device Sharing +Author: FalconForce +Version: '1.0' +Info: |- +Active: true # Enable to run this action +Debug: false # Enable to see query results in the console +SourcePlatform: Neo4j +Query: | + MATCH (u:AZUser) + WHERE u.MfaAuthenticatorDeviceId IS NOT NULL AND u.MfaAuthenticatorDeviceId <> '' + WITH u.MfaAuthenticatorDeviceId AS deviceId, u.MfaAuthenticatorDeviceName as deviceName, collect(u.name) AS userNames, COUNT(u) AS UserCount + WHERE UserCount > 1 + RETURN {DeviceId: deviceId, DeviceName: deviceName ,UserNames: userNames, UserCount: UserCount} AS info + ORDER BY UserCount DESC +Targets: + - Name: CSV + Enabled: true + Path: report/{{date}}/BH-MFA_DEVICE_SHARING_{{date}}.csv + - Name: Markdown + Enabled: true + Path: report/{{date}}/BH-MFA_DEVICE_SHARING_{{date}}.md \ No newline at end of file diff --git a/actions/11-N4J-Reporting/n4j-report-MFA_Email_sharing.yml b/actions/11-N4J-Reporting/n4j-report-MFA_Email_sharing.yml new file mode 100644 index 0000000..efa18bd --- /dev/null +++ b/actions/11-N4J-Reporting/n4j-report-MFA_Email_sharing.yml @@ -0,0 +1,23 @@ +Name: MFA Email Sharing +ID: N4J_REPORT_MFA_EMAIL_SHARING +Description: MFA Email Sharing +Author: FalconForce +Version: '1.0' +Info: |- +Active: true # Enable to run this action +Debug: false # Enable to see query results in the console +SourcePlatform: Neo4j +Query: | + MATCH (u:AZUser) + WHERE u.MfaEmailAddress IS NOT NULL AND u.MfaEmailAddress <> '' + WITH u.MfaEmailAddress AS emailAddress, collect(u.name) AS userNames, COUNT(u) AS UserCount + WHERE UserCount > 1 + RETURN {EmailAddress: emailAddress, UserNames: userNames, UserCount: UserCount} AS info + ORDER BY UserCount DESC +Targets: + - Name: CSV + Enabled: true + Path: report/{{date}}/BH-MFA_EMAIL_SHARING_{{date}}.csv + - Name: Markdown + Enabled: true + Path: report/{{date}}/BH-MFA_EMAIL_SHARING_{{date}}.md \ No newline at end of file diff --git a/actions/11-N4J-Reporting/n4j-report-MFA_Phone_sharing.yml b/actions/11-N4J-Reporting/n4j-report-MFA_Phone_sharing.yml new file mode 100644 index 0000000..6a40e01 --- /dev/null +++ b/actions/11-N4J-Reporting/n4j-report-MFA_Phone_sharing.yml @@ -0,0 +1,23 @@ +Name: MFA Phone Sharing +ID: N4J_REPORT_MFA_PHONE_SHARING +Description: MFA Phone Sharing +Author: FalconForce +Version: '1.0' +Info: |- +Active: true # Enable to run this action +Debug: false # Enable to see query results in the console +SourcePlatform: Neo4j +Query: | + MATCH (u:AZUser) + WHERE u.MfaPhoneNumber IS NOT NULL AND u.MfaPhoneNumber <> '' + WITH u.MfaPhoneNumber AS phoneNumber, collect(u.name) AS userNames, COUNT(u) AS UserCount + WHERE UserCount > 1 + RETURN {PhoneNumber: phoneNumber, UserNames: userNames, UserCount: UserCount} AS info + ORDER BY UserCount DESC +Targets: + - Name: CSV + Enabled: true + Path: report/{{date}}/BH-MFA_PHONE_SHARING_{{date}}.csv + - Name: Markdown + Enabled: true + Path: report/{{date}}/BH-MFA_PHONE_SHARING_{{date}}.md \ No newline at end of file diff --git a/actions/action_schema.json b/actions/action_schema.json index f041889..aaa0d3e 100644 --- a/actions/action_schema.json +++ b/actions/action_schema.json @@ -33,8 +33,10 @@ "Neo4j", "BloodHound", "MSGraph", + "MSGraphApi", "Splunk", - "LogScale" + "LogScale", + "Elastic" ] }, "Version": { @@ -57,7 +59,9 @@ "BloodHound", "BHSession", "LogScale", - "ADX" + "ADX", + "LimaCharlie", + "Markdown" ] }, "Enabled": { @@ -91,6 +95,32 @@ "additionalProperties": false } }, + { + "if": { + "properties": { + "Name": { + "const": "MarkDown" + } + } + }, + "then": { + "properties": { + "Name" : { + "type": "string" + }, + "Enabled": { + "type": "boolean" + }, + "Path": { + "type": "string" + } + }, + "required": [ + "Path" + ], + "additionalProperties": false + } + }, { "if": { "properties": { @@ -150,7 +180,7 @@ "if": { "properties": { "Name": { - "const": "AXD" + "const": "ADX" } } }, @@ -167,6 +197,9 @@ }, "Enabled": { "type": "boolean" + }, + "BatchSize" : { + "type": "integer" } }, "additionalProperties": false diff --git a/cmd/getcreds.go b/cmd/getcreds.go index a038245..55126b5 100644 --- a/cmd/getcreds.go +++ b/cmd/getcreds.go @@ -3,6 +3,7 @@ package cmd import ( "falconhound/internal" "log" + "path/filepath" "reflect" "github.com/spf13/viper" @@ -19,8 +20,15 @@ func setCredValue(credentials *internal.Credentials, field string, value string) func GetCreds(configFile string, keyvaultFlag bool) (theCreds internal.Credentials) { var err error //read config file - viper.SetConfigName(configFile) - viper.AddConfigPath(".") + dir := filepath.Dir(configFile) + fileName := filepath.Base(configFile) + + viper.SetConfigName(fileName) + if configFile == "config.yml" { + viper.AddConfigPath(".") + } else { + viper.AddConfigPath(dir) + } viper.SetConfigType("yml") if err := viper.ReadInConfig(); err != nil { diff --git a/docs/FEATURE_IDEAS.md b/docs/FEATURE_IDEAS.md index d0b0a7c..617e5cb 100644 --- a/docs/FEATURE_IDEAS.md +++ b/docs/FEATURE_IDEAS.md @@ -35,8 +35,8 @@ BH(E) API - under development Generic output processors - [ ] Write BH compatible JSON outputs - [ ] Write markdown outputs -- [ ] Write to storage account -- [ ] Write to ADX +- [x] Write to storage account +- [-] Write to ADX (beta) GraphAPI - [ ] Look into Defender, AAD, Intune, CA policies, ? @@ -54,7 +54,7 @@ GraphAPI - [ ] Add check that if a credential is empty in the config the in/out processor is not used - [ ] Add more logging -- [ ] Add more error handling +- [x] Add more error handling ## Future releases diff --git a/go.mod b/go.mod index e88e36f..f8981d4 100644 --- a/go.mod +++ b/go.mod @@ -3,57 +3,81 @@ module falconhound go 1.20 require ( - github.com/Azure/azure-kusto-go v0.14.2 - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.2 - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1 + github.com/Azure/azure-kusto-go v0.15.0 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.2 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.12.0 - github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/securityinsights/armsecurityinsights v1.1.1 + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/securityinsights/armsecurityinsights v1.2.0 + github.com/elastic/go-elasticsearch/v8 v8.12.0 github.com/gamepat/azure-oauth2-token v0.2.0 github.com/iancoleman/orderedmap v0.3.0 + github.com/microsoft/kiota-abstractions-go v1.5.6 + github.com/microsoftgraph/msgraph-beta-sdk-go v0.94.0 + github.com/microsoftgraph/msgraph-sdk-go-core v1.0.2 github.com/neo4j/neo4j-go-driver/v4 v4.4.7 github.com/santhosh-tekuri/jsonschema v1.2.4 - github.com/spf13/viper v1.16.0 + github.com/spf13/viper v1.18.2 gopkg.in/yaml.v2 v2.4.0 ) require ( github.com/Azure/azure-pipeline-go v0.2.3 // indirect github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 // indirect github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 // indirect - github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0 // indirect - github.com/Azure/azure-storage-queue-go v0.0.0-20230531184854-c06a8eff66fe // indirect + github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.0 // indirect + github.com/Azure/azure-storage-queue-go v0.0.0-20230927153703-648530c9aaf2 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.29 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.23 // indirect github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/cjlapao/common-go v0.0.39 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/elastic/elastic-transport-go/v8 v8.4.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect - github.com/golang-jwt/jwt/v5 v5.0.0 // indirect - github.com/google/uuid v1.3.1 // indirect + github.com/golang-jwt/jwt/v5 v5.2.0 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-ieproxy v0.0.11 // indirect + github.com/microsoft/kiota-authentication-azure-go v1.0.2 // indirect + github.com/microsoft/kiota-http-go v1.3.0 // indirect + github.com/microsoft/kiota-serialization-form-go v1.0.0 // indirect + github.com/microsoft/kiota-serialization-json-go v1.0.6 // indirect + github.com/microsoft/kiota-serialization-multipart-go v1.0.0 // indirect + github.com/microsoft/kiota-serialization-text-go v1.0.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/pelletier/go-toml/v2 v2.0.8 // indirect - github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect - github.com/samber/lo v1.38.1 // indirect + github.com/pelletier/go-toml/v2 v2.1.1 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/samber/lo v1.39.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect - github.com/spf13/afero v1.9.5 // indirect - github.com/spf13/cast v1.5.1 // indirect - github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/subosito/gotenv v1.4.2 // indirect - golang.org/x/crypto v0.17.0 // indirect - golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/sys v0.15.0 // indirect + github.com/std-uritemplate/std-uritemplate/go v0.0.50 // indirect + github.com/stretchr/testify v1.8.4 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + go.opentelemetry.io/otel v1.23.1 // indirect + go.opentelemetry.io/otel/metric v1.23.1 // indirect + go.opentelemetry.io/otel/trace v1.23.1 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.19.0 // indirect + golang.org/x/exp v0.0.0-20240213143201-ec583247a57a // indirect + golang.org/x/net v0.21.0 // indirect + golang.org/x/sys v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index a88e7be..9caaf19 100644 --- a/go.sum +++ b/go.sum @@ -1,65 +1,27 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/azure-kusto-go v0.14.2 h1:dkdHggCp14TCPLHPkWOiWZWWdnNXlaqNfpW6YC/Xnxo= -github.com/Azure/azure-kusto-go v0.14.2/go.mod h1:twZbo+gYmZPDzzMOqExT7rEZ6kyKFvZxqUl3DoTwaIo= +github.com/Azure/azure-kusto-go v0.15.0 h1:Igr6JELHChySkmN/py5OHnqAUFVU004q+HB7oddDrTk= +github.com/Azure/azure-kusto-go v0.15.0/go.mod h1:twZbo+gYmZPDzzMOqExT7rEZ6kyKFvZxqUl3DoTwaIo= github.com/Azure/azure-pipeline-go v0.1.8/go.mod h1:XA1kFWRVhSK+KNFiOhfv83Fv8L9achrP7OxIzeTn1Yg= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.2 h1:t5+QXLCK9SVi0PPdaY0PrFvYUo24KwA0QwxnaHRSVd4= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.2/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1 h1:LNHhpdK7hzUcx/k1LIcuh5k7k1LGIWLQfCjaneSj7Fc= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1/go.mod h1:uE9zaUfEQT/nbQjVi2IblCG9iaLtZsuYZ8ne+PuQ02M= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.2 h1:c4k2FIYIh4xtwqrQwV0Ct1v5+ehlNXj5NI/MWVsiTkQ= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.2/go.mod h1:5FDJtLEO/GxwNgUxbwrY3LP0pEoThTQJtk2oysdXHxM= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 h1:sO0/P7g68FrryJzljemN+6GTssUXdANk6aJ7T1ZxnsQ= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1/go.mod h1:h8hyGFDsU5HMivxiS2iYFZsgDbU9OnnJ163x5UGVKYo= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 h1:LqbJ/WzJUwBf8UiaSzgX7aMclParm9/5Vgp+TY51uBQ= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2/go.mod h1:yInRyqWXAuaPrgI7p70+lDDgh3mlBohis29jGMISnmc= github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.12.0 h1:xnO4sFyG8UH2fElBkcqLTOZsAajvKfnSlgBBW8dXYjw= github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.12.0/go.mod h1:XD3DIOOVgBCO03OleB1fHjgktVRFxlT++KwKgIOewdM= github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 h1:FbH3BbSb4bvGluTesZZ+ttN/MDsnMmQP36OSnDuSXqw= github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1/go.mod h1:9V2j0jn9jDEkCkv8w/bKTNppX/d0FVA1ud77xCIP4KA= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/securityinsights/armsecurityinsights v1.1.1 h1:hIcqZCTfXIlgeA905IhSH4tF+dt2WJ34VHQHAnY2fnI= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/securityinsights/armsecurityinsights v1.1.1/go.mod h1:8pCW7yUVwlcK7fv6r+QMGyO8T6piLa8TjV0JQJvMdEc= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.2.0 h1:Ma67P/GGprNwsslzEH6+Kb8nybI8jpDTm4Wmzu2ReK8= -github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0 h1:nVocQV40OQne5613EeLayJiRAJuKlBGy+m22qWG+WRg= -github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0/go.mod h1:7QJP7dr2wznCMeqIrhMgWGf7XpAQnVrJqDm9nvV3Cu4= -github.com/Azure/azure-storage-queue-go v0.0.0-20230531184854-c06a8eff66fe h1:HGuouUM1533rBXmMtR7qh5pYNSSjUZG90b/MgJAnb/A= -github.com/Azure/azure-storage-queue-go v0.0.0-20230531184854-c06a8eff66fe/go.mod h1:K6am8mT+5iFXgingS9LUc7TmbsW6XBw3nxaRyaMyWc8= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/securityinsights/armsecurityinsights v1.2.0 h1:6o3sVzt4nWIvNkOR93Lfm4itRGEJ+iw+Y884g4ZRSUs= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/securityinsights/armsecurityinsights v1.2.0/go.mod h1:rfdyOaNT9XsqiUH5cKR2+pKzhVljDtOjNkLiPVKBnZY= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.5.0 h1:AifHbc4mg0x9zW52WOpKbsHaDKuRhlI7TVl47thgQ70= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.0 h1:IfFdxTUDiV58iZqPKgyWiz4X4fCxZeQ1pTQPImLYXpY= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.0/go.mod h1:SUZc9YRRHfx2+FAQKNDGrssXehqLpxmwRv2mC/5ntj4= +github.com/Azure/azure-storage-queue-go v0.0.0-20230927153703-648530c9aaf2 h1:G6pzVaX36QLfGvbLSAt8Leb81MiONYT0L03lhABjrPg= +github.com/Azure/azure-storage-queue-go v0.0.0-20230927153703-648530c9aaf2/go.mod h1:K6am8mT+5iFXgingS9LUc7TmbsW6XBw3nxaRyaMyWc8= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.29 h1:I4+HL/JDvErx2LjyzaVxllw2lRDB5/BT2Bm4g20iqYw= @@ -77,126 +39,64 @@ github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+Z github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0 h1:hVeq+yCyUi+MsoO/CU95yqCIcdzra5ovzk8Q2BBpV2M= -github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cjlapao/common-go v0.0.39 h1:bAAUrj2B9v0kMzbAOhzjSmiyDy+rd56r2sy7oEiQLlA= +github.com/cjlapao/common-go v0.0.39/go.mod h1:M3dzazLjTjEtZJbbxoA5ZDiGCiHmpwqW9l4UWaddwOA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/elastic/elastic-transport-go/v8 v8.4.0 h1:EKYiH8CHd33BmMna2Bos1rDNMM89+hdgcymI+KzJCGE= +github.com/elastic/elastic-transport-go/v8 v8.4.0/go.mod h1:YLHer5cj0csTzNFXoNQ8qhtGY1GTvSqPnKWKaqQE3Hk= +github.com/elastic/go-elasticsearch/v8 v8.12.0 h1:krkiCf4peJa7bZwGegy01b5xWWaYpik78wvisTeRO1U= +github.com/elastic/go-elasticsearch/v8 v8.12.0/go.mod h1:wSzJYrrKPZQ8qPuqAqc6KMR4HrBfHnZORvyL+FMFqq0= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gamepat/azure-oauth2-token v0.2.0 h1:PokuUPeoc/qstowdVSsN6UlPLFEapmk3uic4EJ9dPPU= github.com/gamepat/azure-oauth2-token v0.2.0/go.mod h1:0znXBrpWhXlzuz4Edy32LYm0gSM742BREmAiEa9pYQ0= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= -github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= +github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= -github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= @@ -205,6 +105,24 @@ github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3v github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= github.com/mattn/go-ieproxy v0.0.11 h1:MQ/5BuGSgDAHZOJe6YY80IF2UVCfGkwfo6AeD7HtHYo= github.com/mattn/go-ieproxy v0.0.11/go.mod h1:/NsJd+kxZBmjMc5hrJCKMbP57B84rvq9BiDRbtO9AS0= +github.com/microsoft/kiota-abstractions-go v1.5.6 h1:3hd1sACWB2B9grv8KG1T8g/gGQ4A8kTLv91OUxHSxkE= +github.com/microsoft/kiota-abstractions-go v1.5.6/go.mod h1:2WX7Oh8V9SAdZ80OGeE53rcbdys54Pd38rAeDUghrpM= +github.com/microsoft/kiota-authentication-azure-go v1.0.2 h1:tClGeyFZJ+4Bakf8u0euPM4wqy4ethycdOgx3jyH3pI= +github.com/microsoft/kiota-authentication-azure-go v1.0.2/go.mod h1:aTcti0bUJEcq7kBfQG4Sr4ElvRNuaalXcFEu4iEyQ6M= +github.com/microsoft/kiota-http-go v1.3.0 h1:+DhZ6YGi9iDzPYK5v181H8qFthUm38tM6iuL/36EBpY= +github.com/microsoft/kiota-http-go v1.3.0/go.mod h1:K51qqc8bo6iNgJ0J2CGMk8HH//IPhrM/87Z0y2I/7I8= +github.com/microsoft/kiota-serialization-form-go v1.0.0 h1:UNdrkMnLFqUCccQZerKjblsyVgifS11b3WCx+eFEsAI= +github.com/microsoft/kiota-serialization-form-go v1.0.0/go.mod h1:h4mQOO6KVTNciMF6azi1J9QB19ujSw3ULKcSNyXXOMA= +github.com/microsoft/kiota-serialization-json-go v1.0.6 h1:8v8IXMGurLCRYZs1l0Ck75lN0wzKDLko69mNdQGVWeQ= +github.com/microsoft/kiota-serialization-json-go v1.0.6/go.mod h1:I0CiXKgvKDFOO35lQ5VpYmd2nFLXHdJUsHnG8z/TX7A= +github.com/microsoft/kiota-serialization-multipart-go v1.0.0 h1:3O5sb5Zj+moLBiJympbXNaeV07K0d46IfuEd5v9+pBs= +github.com/microsoft/kiota-serialization-multipart-go v1.0.0/go.mod h1:yauLeBTpANk4L03XD985akNysG24SnRJGaveZf+p4so= +github.com/microsoft/kiota-serialization-text-go v1.0.0 h1:XOaRhAXy+g8ZVpcq7x7a0jlETWnWrEum0RhmbYrTFnA= +github.com/microsoft/kiota-serialization-text-go v1.0.0/go.mod h1:sM1/C6ecnQ7IquQOGUrUldaO5wj+9+v7G2W3sQ3fy6M= +github.com/microsoftgraph/msgraph-beta-sdk-go v0.94.0 h1:wyI4kvjonwAGMfTDqlGftUQpjeDvPY+3kxJCWP+JGzM= +github.com/microsoftgraph/msgraph-beta-sdk-go v0.94.0/go.mod h1:TNjUm85Rskez+SRYJkvSS/cuj830NNIMfyN9/HbtY4Y= +github.com/microsoftgraph/msgraph-sdk-go-core v1.0.2 h1:GsZ2bUe+aMdPo9B6ivm0T9vlU9s4ufTScu+GqZnYNNw= +github.com/microsoftgraph/msgraph-sdk-go-core v1.0.2/go.mod h1:3c/v/N/iuH8UWDf4r4Z9FBiSyGeNZ54BHe2y+9Ccxtc= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/neo4j/neo4j-go-driver/v4 v4.4.7 h1:6D0DPI7VOVF6zB8eubY1lav7RI7dZ2mytnr3fj369Ow= @@ -218,383 +136,142 @@ github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= -github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= -github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= -github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= +github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= +github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= -github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA= +github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= github.com/santhosh-tekuri/jsonschema v1.2.4 h1:hNhW8e7t+H1vgY+1QeEQpveR6D4+OwKPXCfD2aieJis= github.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHiuO9LYd+cIxzgEHCQI4= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= -github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= -github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= -github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= -github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= +github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= +github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +github.com/std-uritemplate/std-uritemplate/go v0.0.50 h1:LAE6WYRmLlDXPtEzr152BnD/MHxGCKmcp5D2Pw0NvmU= +github.com/std-uritemplate/std-uritemplate/go v0.0.50/go.mod h1:CLZ1543WRCuUQQjK0BvPM4QrG2toY8xNZUm8Vbt7vTc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= -github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opentelemetry.io/otel v1.23.1 h1:Za4UzOqJYS+MUczKI320AtqZHZb7EqxO00jAHE0jmQY= +go.opentelemetry.io/otel v1.23.1/go.mod h1:Td0134eafDLcTS4y+zQ26GE8u3dEuRBiBCTUIRHaikA= +go.opentelemetry.io/otel/metric v1.23.1 h1:PQJmqJ9u2QaJLBOELl1cxIdPcpbwzbkjfEyelTl2rlo= +go.opentelemetry.io/otel/metric v1.23.1/go.mod h1:mpG2QPlAfnK8yNhNJAxDZruU9Y1/HubbC+KyH8FaCWI= +go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= +go.opentelemetry.io/otel/trace v1.23.1 h1:4LrmmEd8AU2rFvU1zegmvqW7+kWarxtNOPyeL6HmYY8= +go.opentelemetry.io/otel/trace v1.23.1/go.mod h1:4IpnpJFwr1mo/6HL8XIPJaE9y0+u1KcVmuW7dwFSVrI= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE= +golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -607,13 +284,3 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/input_processor/bloodhound.go b/input_processor/bloodhound.go index 10ed371..5dc66af 100644 --- a/input_processor/bloodhound.go +++ b/input_processor/bloodhound.go @@ -30,6 +30,9 @@ func (m *BHProcessor) ExecuteQuery() (internal.QueryResults, error) { } func BHRequest(query string, creds internal.Credentials) (internal.QueryResults, error) { + if creds.BHTokenKey == "" { + return internal.QueryResults{}, fmt.Errorf("BHTokenKey is empty, skipping..") + } // Convert query from a multiline string from the yaml to a single line string so the API can parse it query = strings.ReplaceAll(query, "\n", " ") diff --git a/input_processor/elastic.go b/input_processor/elastic.go new file mode 100644 index 0000000..51fbf6a --- /dev/null +++ b/input_processor/elastic.go @@ -0,0 +1,130 @@ +package input_processor + +import ( + "encoding/json" + "falconhound/internal" + "fmt" + "github.com/elastic/go-elasticsearch/v8" + "io" + "log" + "strings" +) + +type ElasticConfig struct { +} + +type ElasticProcessor struct { + *InputProcessor + Config ElasticConfig +} + +type ElasticsearchResponse struct { + Hits struct { + Hits []struct { + Source json.RawMessage `json:"_source"` + } `json:"hits"` + } `json:"hits"` +} + +func (m *ElasticProcessor) ExecuteQuery() (internal.QueryResults, error) { + if m.Credentials.ElasticApiKey == "" { + return internal.QueryResults{}, fmt.Errorf("ElasticApiKey is empty, skipping..") + } + + results, err := queryElastic(m.Query, m.Credentials, m.Debug) + if err != nil { + if strings.Contains(err.Error(), "unexpected HTTP status code: 400") { + return internal.QueryResults{}, fmt.Errorf("failed to run query %q. Most likely there is a syntax error in the query", m.Query) + } else { + return internal.QueryResults{}, fmt.Errorf("failed to run query %q: %w", m.Query, err) + } + } + + var ElasticResults internal.QueryResults + + err = json.Unmarshal([]byte(results), &ElasticResults) + if err != nil { + return internal.QueryResults{}, fmt.Errorf("failed to unmarshal JSON from Elastic: %v", err) + } + + return ElasticResults, nil +} + +func queryElastic(query string, credentials internal.Credentials, debug bool) (string, error) { + + cfg := elasticsearch.Config{ + CloudID: credentials.ElasticCloudID, + APIKey: credentials.ElasticApiKey, + } + es, err := elasticsearch.NewClient(cfg) + if err != nil { + log.Fatalf("Error creating the client: %s", err) + } + + cleanquery := strings.ReplaceAll(query, "\n", " ") + esquery := fmt.Sprintf(`{ "query": { "query_string": { "query": "(%s)" }}}`, cleanquery) + fmt.Printf("Query: %s\n", esquery) + res, err := es.Search( + es.Search.WithIndex(".ds-logs-system.security-default*"), // <-- TODO: now with index + es.Search.WithBody(strings.NewReader(esquery)), + es.Search.WithTrackTotalHits(true), + es.Search.WithPretty(), + ) + if err != nil { + log.Fatalf("Error getting response: %s", err) + } + + defer res.Body.Close() + //log.Println(res) + + bodyBytes, _ := io.ReadAll(res.Body) + + var esResponse ElasticsearchResponse + if err := json.Unmarshal([]byte(bodyBytes), &esResponse); err != nil { + fmt.Printf("Error parsing JSON: %v\n", err) + //return + } + + var allFlattenedData []map[string]interface{} + + for _, hit := range esResponse.Hits.Hits { + var result map[string]interface{} + err := json.Unmarshal(hit.Source, &result) + if err != nil { + log.Fatalf("Error unmarshalling JSON: %v", err) + } + + flattenedData := make(map[string]interface{}) + flatten(result, "", flattenedData) + + allFlattenedData = append(allFlattenedData, flattenedData) + } + + flattenedJSON, err := json.Marshal(allFlattenedData) + if err != nil { + log.Fatalf("Error marshalling flattened data: %v", err) + } + + return string(flattenedJSON), nil +} + +func flatten(data interface{}, prefix string, flattenedData map[string]interface{}) { + switch data := data.(type) { + case map[string]interface{}: + for k, v := range data { + var fullKey string + if prefix == "" { + fullKey = k + } else { + fullKey = prefix + "." + k + } + flatten(v, fullKey, flattenedData) + } + case []interface{}: + for i, v := range data { + flatten(v, fmt.Sprintf("%s.%d", prefix, i), flattenedData) + } + default: + flattenedData[prefix] = data + } +} diff --git a/input_processor/input_cmd/graph_getdynamicgroups.go b/input_processor/input_cmd/graph_getdynamicgroups.go new file mode 100644 index 0000000..7cfbb02 --- /dev/null +++ b/input_processor/input_cmd/graph_getdynamicgroups.go @@ -0,0 +1,67 @@ +package input_cmd + +import ( + "context" + "encoding/json" + "fmt" + msgraphsdk "github.com/microsoftgraph/msgraph-beta-sdk-go" + graphgroups "github.com/microsoftgraph/msgraph-beta-sdk-go/groups" + "github.com/microsoftgraph/msgraph-beta-sdk-go/models" + msgraphcore "github.com/microsoftgraph/msgraph-sdk-go-core" +) + +type DynamicGroups struct { + GroupType string + MembershipRule string + ObjectId string + MembershipRuleProcessingState string + DisplayName string + TenantId string +} + +// requires Directory.Read.All +func GetDynamicGroups(graphClient msgraphsdk.GraphServiceClient) ([]byte, error) { + + requestFilter := "mailEnabled eq false and securityEnabled eq true and membershipRuleProcessingState eq 'On'" + requestCount := true + + requestParameters := &graphgroups.GroupsRequestBuilderGetQueryParameters{ + Filter: &requestFilter, + Count: &requestCount, + Select: []string{"id", "membershipRule", "membershipRuleProcessingState", "groupTypes", "displayName", "organizationId"}, + } + configuration := &graphgroups.GroupsRequestBuilderGetRequestConfiguration{ + QueryParameters: requestParameters, + } + + dynamicGroups, err := graphClient.Groups().Get(context.Background(), configuration) + if err != nil { + fmt.Println("Error dynamic groups:", err) + return nil, err + } + settingsperGroup := make([]DynamicGroups, 0) + + pageIterator, err := msgraphcore.NewPageIterator[*models.Group](dynamicGroups, graphClient.GetAdapter(), models.CreateGroupCollectionResponseFromDiscriminatorValue) + err = pageIterator.Iterate(context.Background(), func(pageItem *models.Group) bool { + settings := DynamicGroups{} + // no need for nil check as we are filtering on these fields, also dynamic groups can only have one group type + settings.GroupType = pageItem.GetGroupTypes()[0] + settings.MembershipRule = *pageItem.GetMembershipRule() + settings.ObjectId = *pageItem.GetId() + settings.MembershipRuleProcessingState = *pageItem.GetMembershipRuleProcessingState() + settings.DisplayName = *pageItem.GetDisplayName() + settings.TenantId = *pageItem.GetOrganizationId() + settingsperGroup = append(settingsperGroup, settings) + return true + }) + if err != nil { + fmt.Println("Error iterating dynamic group requests:", err) + return nil, err + } + json, err := json.MarshalIndent(settingsperGroup, "", " ") + if err != nil { + fmt.Println("Error converting to JSON:", err) + return nil, err + } + return json, nil +} diff --git a/input_processor/input_cmd/graph_getmfa.go b/input_processor/input_cmd/graph_getmfa.go new file mode 100644 index 0000000..296a8c4 --- /dev/null +++ b/input_processor/input_cmd/graph_getmfa.go @@ -0,0 +1,149 @@ +package input_cmd + +import ( + "context" + "encoding/json" + "fmt" + abstractions "github.com/microsoft/kiota-abstractions-go" + msgraphsdk "github.com/microsoftgraph/msgraph-beta-sdk-go" + "github.com/microsoftgraph/msgraph-beta-sdk-go/models" + graphusers "github.com/microsoftgraph/msgraph-beta-sdk-go/users" + msgraphcore "github.com/microsoftgraph/msgraph-sdk-go-core" +) + +func GetMFA(graphClient msgraphsdk.GraphServiceClient) ([]byte, error) { + // Requires User.Read.All and UserAuthenticationMethod.Read.All permissions + // or one of the following roles: Global Reader, Privileged Authentication Administrator + // Authentication Administrator is also possible, but only shows masked phone numbers and email addresses. + // Both Administrator roles are not recommended for production use, since they have way too many permissions. + + type MFASettings struct { + SignInPreference string + AuthMethods []string + MfaAuthMethods []string + PhoneNumber string + SmsSignInState string + HelloDevice string + ObjectId string + UserName string + AuthenticatorDeviceName string + AuthenticatorDeviceId string + FidoDeviceName string + FidoModel string + MfaEmailAddress string + } + + conversionMap := map[string]string{ + "#microsoft.graph.phoneAuthenticationMethod": "Phone", + "#microsoft.graph.windowsHelloForBusinessAuthenticationMethod": "WindowsHelloForBusiness", + "#microsoft.graph.microsoftAuthenticatorAuthenticationMethod": "MicrosoftAuthenticator", + "#microsoft.graph.fido2AuthenticationMethod": "Fido2", + "#microsoft.graph.softwareOathAuthenticationMethod": "SoftwareOath", + "#microsoft.graph.emailAuthenticationMethod": "Email", + "#microsoft.graph.passwordAuthenticationMethod": "Password", + "#microsoft.graph.passwordlessMicrosoftAuthenticatorMethods": "PasswordlessMicrosoftAuthenticator", + } + + mfaSettingsPerUser := make([]MFASettings, 0) + + headers := abstractions.NewRequestHeaders() + headers.Add("ConsistencyLevel", "eventual") + // only get required information to conserve memory and bandwith + requestParameters := &graphusers.UsersRequestBuilderGetQueryParameters{ + Select: []string{"id", "displayName"}, + } + configuration := &graphusers.UsersRequestBuilderGetRequestConfiguration{ + Headers: headers, + QueryParameters: requestParameters, + } + + usersResponse, err := graphClient.Users().Get(context.Background(), configuration) + if err != nil { + fmt.Println("Error getting users:", err) + fmt.Println("Make sure to assign the User.Read.All and UserAuthenticationMethod.Read.All permissions to the app.") + return nil, err + } + + pageIterator, err := msgraphcore.NewPageIterator[models.Userable](usersResponse, graphClient.GetAdapter(), models.CreateUserCollectionResponseFromDiscriminatorValue) + + err = pageIterator.Iterate(context.Background(), func(user models.Userable) bool { + settings := MFASettings{} + settings.ObjectId = *user.GetId() + settings.UserName = *user.GetDisplayName() + methods, err := graphClient.Users().ByUserId(*user.GetId()).Authentication().Methods().Get(context.Background(), nil) + if err != nil { + fmt.Println("Error getting MFA Authentication methods for user", *user.GetId(), ":", err) + return false + } + signInPreferences, err := graphClient.Users().ByUserId(*user.GetId()).Authentication().SignInPreferences().Get(context.Background(), nil) + if err != nil { + fmt.Println("Error getting MFA Authentication methods for user", *user.GetId(), ":", err) + return false + } + + if signInPreferences.GetUserPreferredMethodForSecondaryAuthentication() != nil { + settings.SignInPreference = (*signInPreferences.GetUserPreferredMethodForSecondaryAuthentication()).String() + } else { + settings.SignInPreference = "no-mfa" + } + + authMethods := methods.GetValue() + for _, method := range authMethods { + methodType := *method.GetOdataType() + if methodType == "#microsoft.graph.passwordAuthenticationMethod" { + // Skip this iteration if methodType is "#microsoft.graph.passwordAuthenticationMethod" + continue + } + if newValue, ok := conversionMap[methodType]; ok { + settings.AuthMethods = append(settings.AuthMethods, newValue) + } else { + settings.AuthMethods = append(settings.AuthMethods, methodType) + } + if *method.GetOdataType() == "#microsoft.graph.phoneAuthenticationMethod" { + phone, _ := graphClient.Users().ByUserId(*user.GetId()).Authentication().PhoneMethods().Get(context.Background(), nil) + phoneData := phone.GetValue() + settings.PhoneNumber = *phoneData[0].GetPhoneNumber() + settings.SmsSignInState = (*phoneData[0].GetSmsSignInState()).String() + } + if *method.GetOdataType() == "#microsoft.graph.windowsHelloForBusinessAuthenticationMethod" { + hello, _ := graphClient.Users().ByUserId(*user.GetId()).Authentication().WindowsHelloForBusinessMethods().Get(context.Background(), nil) + helloData := hello.GetValue() + settings.HelloDevice = *helloData[0].GetDisplayName() + } + if *method.GetOdataType() == "#microsoft.graph.microsoftAuthenticatorAuthenticationMethod" || *method.GetOdataType() == "#microsoft.graph.passwordlessMicrosoftAuthenticatorMethods" { + ma, _ := graphClient.Users().ByUserId(*user.GetId()).Authentication().MicrosoftAuthenticatorMethods().Get(context.Background(), nil) + maData := ma.GetValue() + settings.AuthenticatorDeviceName = *maData[0].GetDisplayName() + settings.AuthenticatorDeviceId = *maData[0].GetId() + } + if *method.GetOdataType() == "#microsoft.graph.fido2AuthenticationMethod" { + fido, _ := graphClient.Users().ByUserId(*user.GetId()).Authentication().Fido2Methods().Get(context.Background(), nil) + fidoData := fido.GetValue() + settings.FidoDeviceName = *fidoData[0].GetDisplayName() + settings.FidoModel = *fidoData[0].GetModel() + } + if *method.GetOdataType() == "#microsoft.graph.emailAuthenticationMethod" { + email, _ := graphClient.Users().ByUserId(*user.GetId()).Authentication().EmailMethods().Get(context.Background(), nil) + emailData := email.GetValue() + settings.MfaEmailAddress = *emailData[0].GetEmailAddress() + } + // add the auth methods to a single string for easier ingestion into neo4j + settings.MfaAuthMethods = settings.AuthMethods + } + mfaSettingsPerUser = append(mfaSettingsPerUser, settings) + return true + }) + if err != nil { + fmt.Println("Error iterating over users:", err) + return nil, err + } + + json, err := json.MarshalIndent(mfaSettingsPerUser, "", " ") + if err != nil { + fmt.Println("Error converting to JSON:", err) + return nil, err + } + + return json, nil + +} diff --git a/input_processor/input_cmd/graph_getoauthconsent.go b/input_processor/input_cmd/graph_getoauthconsent.go new file mode 100644 index 0000000..7699005 --- /dev/null +++ b/input_processor/input_cmd/graph_getoauthconsent.go @@ -0,0 +1,53 @@ +package input_cmd + +import ( + "context" + "encoding/json" + "fmt" + msgraphsdk "github.com/microsoftgraph/msgraph-beta-sdk-go" + "github.com/microsoftgraph/msgraph-beta-sdk-go/models" + msgraphcore "github.com/microsoftgraph/msgraph-sdk-go-core" + "time" +) + +type OauthConsent struct { + ClientId string + ConsentType string + StartTime time.Time + ExpiryTime time.Time + ResourceId string + Scope string +} + +// requires Directory.Read.All +func GetOAuthConsent(graphClient msgraphsdk.GraphServiceClient) ([]byte, error) { + userRiskDetection, err := graphClient.Oauth2PermissionGrants().Get(context.Background(), nil) + if err != nil { + fmt.Println("Error getting consented apps:", err) + return nil, err + } + oauthconsentperAccount := make([]OauthConsent, 0) + + pageIterator, err := msgraphcore.NewPageIterator[*models.OAuth2PermissionGrant](userRiskDetection, graphClient.GetAdapter(), models.CreateOAuth2PermissionGrantCollectionResponseFromDiscriminatorValue) + err = pageIterator.Iterate(context.Background(), func(pageItem *models.OAuth2PermissionGrant) bool { + settings := OauthConsent{} + settings.ClientId = *pageItem.GetClientId() + settings.ConsentType = *pageItem.GetConsentType() + settings.StartTime = *pageItem.GetStartTime() + settings.ExpiryTime = *pageItem.GetExpiryTime() + settings.ResourceId = *pageItem.GetResourceId() + settings.Scope = *pageItem.GetScope() + oauthconsentperAccount = append(oauthconsentperAccount, settings) + return true + }) + if err != nil { + fmt.Println("Error iterating role app consents:", err) + return nil, err + } + json, err := json.MarshalIndent(oauthconsentperAccount, "", " ") + if err != nil { + fmt.Println("Error converting to JSON:", err) + return nil, err + } + return json, nil +} diff --git a/input_processor/logscale.go b/input_processor/logscale.go index c7fd28d..5494afd 100644 --- a/input_processor/logscale.go +++ b/input_processor/logscale.go @@ -20,6 +20,10 @@ type LogScaleProcessor struct { } func (m *LogScaleProcessor) ExecuteQuery() (internal.QueryResults, error) { + if m.Credentials.LogScaleToken == "" { + return internal.QueryResults{}, fmt.Errorf("LogScaleToken is empty, skipping..") + } + results, err := queryLogscale(m.Query, m.Credentials, m.Debug) if err != nil { if strings.Contains(err.Error(), "unexpected HTTP status code: 400") { diff --git a/input_processor/mde.go b/input_processor/mde.go index 4ada594..2ff5d94 100644 --- a/input_processor/mde.go +++ b/input_processor/mde.go @@ -39,6 +39,10 @@ type MDESession struct { var _MDESession MDESession func (m *MDEProcessor) ExecuteQuery() (internal.QueryResults, error) { + if m.Credentials.MDEAppSecret == "" { + return internal.QueryResults{}, fmt.Errorf("MDEAppSecret is empty, skipping..") + } + if !_MDESession.initialized { _MDESession.token = MDEToken(m.Credentials) _MDESession.initialized = true diff --git a/input_processor/msgraph.go b/input_processor/msgraph.go index 8a865fd..c0e4829 100644 --- a/input_processor/msgraph.go +++ b/input_processor/msgraph.go @@ -36,6 +36,10 @@ type MSGraphResults struct { var _MSGraphSession MSGraphSession func (m *MSGraphProcessor) ExecuteQuery() (internal.QueryResults, error) { + if m.Credentials.GraphAppSecret == "" { + return internal.QueryResults{}, fmt.Errorf("GraphAppSecret is empty, skipping..") + } + if !_MSGraphSession.initialized { _MSGraphSession.token = graphToken(m.Credentials) _MSGraphSession.initialized = true diff --git a/input_processor/msgraphapi.go b/input_processor/msgraphapi.go new file mode 100644 index 0000000..57d7526 --- /dev/null +++ b/input_processor/msgraphapi.go @@ -0,0 +1,108 @@ +package input_processor + +import ( + "encoding/json" + "falconhound/input_processor/input_cmd" + "falconhound/internal" + "fmt" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + msgraphsdk "github.com/microsoftgraph/msgraph-beta-sdk-go" + "strings" +) + +type MsGraphApiConfig struct { +} + +type MsGraphApiProcessor struct { + *InputProcessor + Config MsGraphApiConfig +} + +type MSGraphApiSession struct { + initialized bool + client msgraphsdk.GraphServiceClient +} + +var _MSGraphApiSession MSGraphApiSession + +type MsGraphApiResults struct { + Schema []struct { + Context string `json:"@odata.context"` + } `json:"Schema"` + Results internal.QueryResults `json:"Value"` +} + +func (m *MsGraphApiProcessor) ExecuteQuery() (internal.QueryResults, error) { + if m.Credentials.GraphAppSecret == "" { + return internal.QueryResults{}, fmt.Errorf("GraphAppSecret is empty, skipping..") + } + + if !_MSGraphApiSession.initialized { + _MSGraphApiSession.client = graphClient(m.Credentials) + _MSGraphApiSession.initialized = true + } + + var MsGraphApiResults internal.QueryResults + m.Query = strings.TrimSpace(m.Query) + switch m.Query { + case "GetMFA": + results, err := input_cmd.GetMFA(_MSGraphApiSession.client) + if err != nil { + return internal.QueryResults{}, fmt.Errorf("GetMFA failed: %v", err) + } + err = json.Unmarshal([]byte(results), &MsGraphApiResults) + if err != nil { + return internal.QueryResults{}, fmt.Errorf("failed to unmarshal JSON: %v", err) + } + return MsGraphApiResults, nil + //case "GetUserRisk": + // results, err := input_cmd.GetUserRisk(_MSGraphApiSession.client) + // if err != nil { + // return internal.QueryResults{}, fmt.Errorf("GetUserRisk failed: %v", err) + // } + // err = json.Unmarshal([]byte(results), &MsGraphApiResults) + // if err != nil { + // return internal.QueryResults{}, fmt.Errorf("failed to unmarshal JSON: %v", err) + // } + // return MsGraphApiResults, nil + case "GetOAuthConsent": + results, err := input_cmd.GetOAuthConsent(_MSGraphApiSession.client) + if err != nil { + return internal.QueryResults{}, fmt.Errorf("GetOAuthConsent failed: %v", err) + } + err = json.Unmarshal([]byte(results), &MsGraphApiResults) + if err != nil { + return internal.QueryResults{}, fmt.Errorf("failed to unmarshal JSON: %v", err) + } + return MsGraphApiResults, nil + case "GetDynamicGroups": + results, err := input_cmd.GetDynamicGroups(_MSGraphApiSession.client) + if err != nil { + return internal.QueryResults{}, fmt.Errorf("GetDynamicGroups failed: %v", err) + } + err = json.Unmarshal([]byte(results), &MsGraphApiResults) + if err != nil { + return internal.QueryResults{}, fmt.Errorf("failed to unmarshal JSON: %v", err) + + } + return MsGraphApiResults, nil + } + + return nil, fmt.Errorf("Query not found: %s", m.Query) +} + +func graphClient(creds internal.Credentials) msgraphsdk.GraphServiceClient { + cred, err := azidentity.NewClientSecretCredential(creds.GraphTenantID, creds.GraphAppID, creds.GraphAppSecret, nil) + if err != nil { + fmt.Println("err") + panic(err) + } + + graphClient, err := msgraphsdk.NewGraphServiceClientWithCredentials(cred, []string{"https://graph.microsoft.com/.default"}) + if err != nil { + fmt.Println("Error creating the client:", err) + panic(err) + } + + return *graphClient +} diff --git a/input_processor/neo4j.go b/input_processor/neo4j.go index d7288b6..416d9f9 100644 --- a/input_processor/neo4j.go +++ b/input_processor/neo4j.go @@ -3,6 +3,7 @@ package input_processor import ( "encoding/json" "falconhound/internal" + "fmt" "log" "github.com/neo4j/neo4j-go-driver/v4/neo4j" @@ -17,6 +18,10 @@ type Neo4jProcessor struct { } func (m *Neo4jProcessor) ExecuteQuery() (internal.QueryResults, error) { + if m.Credentials.Neo4jPassword == "" { + return internal.QueryResults{}, fmt.Errorf("Neo4jPassword is empty, skipping..") + } + results, err := ReadNeo4j(m.Query, m.Credentials) if err != nil { return internal.QueryResults{}, err diff --git a/input_processor/sentinel.go b/input_processor/sentinel.go index c700f2e..8ad9e56 100644 --- a/input_processor/sentinel.go +++ b/input_processor/sentinel.go @@ -33,6 +33,10 @@ type SentinelResults struct { } func (m *SentinelProcessor) ExecuteQuery() (internal.QueryResults, error) { + if m.Credentials.SentinelAppSecret == "" { + return internal.QueryResults{}, fmt.Errorf("SentinelAppSecret is empty, skipping..") + } + results, err := LArunQuery(m.Query, m.Credentials) if err != nil { if strings.Contains(err.Error(), "unexpected HTTP status code: 400") { diff --git a/input_processor/splunk.go b/input_processor/splunk.go index d7ceb56..c19dc07 100644 --- a/input_processor/splunk.go +++ b/input_processor/splunk.go @@ -42,6 +42,10 @@ type SplunkProcessor struct { } func (m *SplunkProcessor) ExecuteQuery() (internal.QueryResults, error) { + if m.Credentials.SplunkApiToken == "" { + return internal.QueryResults{}, fmt.Errorf("SplunkApiToken is empty, skipping..") + } + results, err := QuerySplunk(m.Query, m.Credentials, m.Debug) if err != nil { if strings.Contains(err.Error(), "unexpected HTTP status code: 400") { diff --git a/internal/credentials.go b/internal/credentials.go index 5cca628..d08ff00 100644 --- a/internal/credentials.go +++ b/internal/credentials.go @@ -38,4 +38,9 @@ type Credentials struct { LogScaleUrl string `config:"logscale.url"` LogScaleToken string `config:"logscale.token"` LogScaleRepository string `config:"logscale.repository"` + LimaCharlieAPIUrl string `config:"limacharlie.apiurl"` + LimaCharlieOrgId string `config:"limacharlie.orgid"` + LimaCharlieIngestKey string `config:"limacharlie.ingestkey"` + ElasticCloudID string `config:"elastic.cloudid"` + ElasticApiKey string `config:"elastic.apikey"` } diff --git a/internal/version.go b/internal/version.go index 4d159d9..2e80e99 100644 --- a/internal/version.go +++ b/internal/version.go @@ -1,3 +1,3 @@ package internal -const Version = "FalconHound v1.2.0" +const Version = "FalconHound v1.3.0" diff --git a/main.go b/main.go index 66a5bd3..05ecfd7 100644 --- a/main.go +++ b/main.go @@ -184,6 +184,17 @@ func makeOutputProcessor(target Target, query Query, credentials internal.Creden Path: target.Path, }, }, nil + case "Markdown": + return &output_processor.MDOutputProcessor{ + OutputProcessor: &baseOutput, + Config: output_processor.MDOutputConfig{ + Path: target.Path, + QueryName: query.Name, + QueryDescription: query.Description, + QueryEventID: query.ID, + QueryDate: time.Now().Format("2006-01-02"), + }, + }, nil case "Sentinel": return &output_processor.SentinelOutputProcessor{ OutputProcessor: &baseOutput, @@ -203,6 +214,7 @@ func makeOutputProcessor(target Target, query Query, credentials internal.Creden QueryEventID: query.ID, BHQuery: target.BHQuery, Table: target.Table, + BatchSize: target.BatchSize, }, }, nil case "Splunk": @@ -210,6 +222,16 @@ func makeOutputProcessor(target Target, query Query, credentials internal.Creden OutputProcessor: &baseOutput, Config: output_processor.SplunkOutputConfig{}, }, nil + case "LimaCharlie": + return &output_processor.LimaCharlieOutputProcessor{ + OutputProcessor: &baseOutput, + Config: output_processor.LimaCharlieOutputConfig{ + QueryName: query.Name, + QueryDescription: query.Description, + QueryEventID: query.ID, + BHQuery: target.BHQuery, + }, + }, nil case "Neo4j": return &output_processor.Neo4jOutputProcessor{ OutputProcessor: &baseOutput, @@ -277,6 +299,11 @@ func makeInputProcessor(query Query, credentials internal.Credentials, outputs [ InputProcessor: &baseProcessor, Config: input_processor.MSGraphConfig{}, }, nil + case "MSGraphApi": + return &input_processor.MsGraphApiProcessor{ + InputProcessor: &baseProcessor, + Config: input_processor.MsGraphApiConfig{}, + }, nil case "Splunk": return &input_processor.SplunkProcessor{ InputProcessor: &baseProcessor, @@ -287,6 +314,11 @@ func makeInputProcessor(query Query, credentials internal.Credentials, outputs [ InputProcessor: &baseProcessor, Config: input_processor.LogScaleConfig{}, }, nil + case "Elastic": + return &input_processor.ElasticProcessor{ + InputProcessor: &baseProcessor, + Config: input_processor.ElasticConfig{}, + }, nil default: return nil, fmt.Errorf("source platform %q not supported", query.SourcePlatform) } diff --git a/output_processor/adx.go b/output_processor/adx.go index 752e294..7edd871 100644 --- a/output_processor/adx.go +++ b/output_processor/adx.go @@ -8,6 +8,7 @@ import ( "github.com/Azure/azure-kusto-go/kusto" "github.com/Azure/azure-kusto-go/kusto/ingest" "log" + "time" ) type ADXOutputConfig struct { @@ -16,6 +17,7 @@ type ADXOutputConfig struct { QueryDescription string QueryEventID string Table string + BatchSize int } type ADXOutputProcessor struct { @@ -24,6 +26,9 @@ type ADXOutputProcessor struct { } func (m *ADXOutputProcessor) BatchSize() int { + if m.Config.BatchSize > 0 { + return m.Config.BatchSize + } return 1 } @@ -34,12 +39,14 @@ func (m *ADXOutputProcessor) ProduceOutput(QueryResults internal.QueryResults) e return err } + runTime := time.Now().UTC().Format("2006-01-02T15:04:05.0000000Z07:00") // Create a data object ADXdata with data from EventData or EnrichmentData, whichever is not nil ADXData := map[string]interface{}{ "Name": m.Config.QueryName, "Description": m.Config.QueryDescription, "EventID": m.Config.QueryEventID, "BHQuery": m.Config.BHQuery, + "Timestamp": runTime, "EventData": string(jsonData), } diff --git a/output_processor/csv.go b/output_processor/csv.go index cd8537c..ad4a3b3 100644 --- a/output_processor/csv.go +++ b/output_processor/csv.go @@ -5,7 +5,10 @@ import ( "falconhound/internal" "fmt" "os" + "reflect" "sort" + "strings" + "time" ) type CSVOutputConfig struct { @@ -29,6 +32,13 @@ func (m *CSVOutputProcessor) ProduceOutput(QueryResults internal.QueryResults) e // WriteCSV writes the results to a CSV file func WriteCSV(results internal.QueryResults, path string) error { + //replace {{date}} with the current date if it exists + path = strings.Replace(path, "{{date}}", time.Now().Format("2006-01-02"), 2) + // create the folder if it doesn't exist + err := os.MkdirAll(path[:strings.LastIndex(path, "/")], 0755) + if err != nil { + return fmt.Errorf("failed creating folder: %w", err) + } // Create a file for writing csvFile, err := os.Create(path) if err != nil { @@ -58,6 +68,20 @@ func WriteCSV(results internal.QueryResults, path string) error { if !ok { v = nil } + // Check if the value is a slice + if reflect.TypeOf(v).Kind() == reflect.Slice { + // Check if the slice elements are of type string + if reflect.TypeOf(v).Elem().Kind() == reflect.String { + v = strings.Join(v.([]string), ", ") + } else if reflect.TypeOf(v).Elem().Kind() == reflect.Interface { + // Handle the case where the slice elements are of type interface{} + var strSlice []string + for _, elem := range v.([]interface{}) { + strSlice = append(strSlice, fmt.Sprintf("%v", elem)) + } + v = strings.Join(strSlice, ", ") + } + } row = append(row, fmt.Sprintf("%v", v)) } err := csvWriter.Write(row) diff --git a/output_processor/limacharlie.go b/output_processor/limacharlie.go new file mode 100644 index 0000000..b8552e5 --- /dev/null +++ b/output_processor/limacharlie.go @@ -0,0 +1,78 @@ +package output_processor + +import ( + "bytes" + "crypto/tls" + "encoding/json" + "falconhound/internal" + "fmt" + "net/http" +) + +type LimaCharlieOutputConfig struct { + BHQuery string + QueryName string + QueryDescription string + QueryEventID string +} + +type LimaCharlieOutputProcessor struct { + *OutputProcessor + Config LimaCharlieOutputConfig +} + +func (m *LimaCharlieOutputProcessor) BatchSize() int { + return 0 +} + +func (m *LimaCharlieOutputProcessor) ProduceOutput(QueryResults internal.QueryResults) error { + jsonData, err := json.Marshal(QueryResults) + if err != nil { + return err + } + LimaCharlieData := map[string]interface{}{ + "Name": m.Config.QueryName, + "Description": m.Config.QueryDescription, + "EventID": m.Config.QueryEventID, + "BHQuery": m.Config.BHQuery, + "EventData": string(jsonData), + } + // Wrap LimaCharlieData in JSON + LimaCharlieJSONData, err := json.Marshal(LimaCharlieData) + if err != nil { + } + + url := (m.Credentials.LimaCharlieAPIUrl) + fmt.Println(url) + // Create HTTP request + req, err := http.NewRequest("POST", url, bytes.NewBuffer(LimaCharlieJSONData)) + if err != nil { + return err + } + + // Set headers + req.SetBasicAuth(m.Credentials.LimaCharlieOrgId, m.Credentials.LimaCharlieIngestKey) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("lc-source", "FalconHound") + req.Header.Set("lc-hint", "json") + + // Create HTTP client with custom transport to disable SSL verification + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + client := &http.Client{Transport: tr} + + // Send HTTP request + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + // Check response status code + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("unexpected status code: %d", resp.StatusCode) + } + + return nil +} diff --git a/output_processor/markdown.go b/output_processor/markdown.go new file mode 100644 index 0000000..787ea59 --- /dev/null +++ b/output_processor/markdown.go @@ -0,0 +1,88 @@ +package output_processor + +import ( + "bufio" + "falconhound/internal" + "fmt" + "os" + "strings" + "time" +) + +type MDOutputConfig struct { + Path string + QueryName string + QueryEventID string + QueryDescription string + QueryDate string +} + +type MDOutputProcessor struct { + *OutputProcessor + Config MDOutputConfig +} + +// MD does not require batching, will write all output in one go +func (m *MDOutputProcessor) BatchSize() int { + return 0 +} + +func (m *MDOutputProcessor) ProduceOutput(QueryResults internal.QueryResults) error { + err := WriteMD(QueryResults, m.Config) + return err +} + +// WriteMD writes the results to a MD file +func WriteMD(results internal.QueryResults, config MDOutputConfig) error { + //replace {{date}} with the current date if it exists + path := strings.Replace(config.Path, "{{date}}", time.Now().Format("2006-01-02"), 2) + // create the folder if it doesn't exist + err := os.MkdirAll(path[:strings.LastIndex(path, "/")], 0755) + if err != nil { + return fmt.Errorf("failed creating folder: %w", err) + } + // Create a file for writing + MDFile, err := os.Create(path) + if err != nil { + return fmt.Errorf("failed creating file: %w", err) + } + MDWriter := bufio.NewWriter(MDFile) + + var headers []string + if len(results) == 0 { + return nil + } + for key := range results[0] { + headers = append(headers, key) + } + + table := fmt.Sprintf("# Results for query: %s\n\n", config.QueryEventID) + table += fmt.Sprintf("## %s\n\n", config.QueryName) + table += fmt.Sprintf("Description: %s\n", config.QueryDescription) + table += fmt.Sprintf("Date: %s\n\n", config.QueryDate) + table += "| " + strings.Join(headers, " | ") + " |\n" + table += "| " + strings.Repeat("--- | ", len(headers)) + "\n" + _, err = MDWriter.WriteString(table) + if err != nil { + return fmt.Errorf("failed writing to file: %w", err) + } + + // Generate the table rows + for _, row := range results { + var values []string + for _, header := range headers { + value := fmt.Sprintf("%v", row[header]) + values = append(values, value) + } + tablerow := "| " + strings.Join(values, " | ") + " |\n" + _, err = MDWriter.WriteString(tablerow) + if err != nil { + return fmt.Errorf("failed writing row to file: %w", err) + } + } + + MDWriter.Flush() + MDFile.Close() + + return nil +}