├── .gitignore
├── .vscode
├── extensions.json
├── launch.json
├── settings.json
└── tasks.json
├── LICENSE
├── README.md
├── event-hubs-hec
├── .funcignore
├── .gitignore
├── README.md
├── aad-logs
│ ├── function.json
│ └── index.js
├── aad-signin-logs-non-interactive
│ ├── function.json
│ └── index.js
├── aad-signin-logs-service-principal
│ ├── function.json
│ └── index.js
├── activity-logs
│ ├── function.json
│ └── index.js
├── deploy
│ ├── azureDeploy.json
│ └── azureDeploy.portal.json
├── diagnostic-logs
│ ├── function.json
│ └── index.js
├── docs
│ ├── activity_log_diagnostic_settings.md
│ ├── azure_ad_diagnostic_settings.md
│ ├── diagnostic_logs_settings.md
│ ├── images
│ │ ├── AAD_Event_Hub.png
│ │ └── Activity_Logs_Diagnostic_Settings.png
│ └── metrics_settings.md
├── helpers
│ ├── config.js
│ └── splunk.js
├── host.json
├── metrics
│ ├── function.json
│ └── index.js
├── package-lock.json
├── package.json
└── proxies.json
├── graph
├── .funcignore
├── LICENSE
├── README.md
├── create-subscription
│ ├── function.json
│ └── index.js
├── delete-subscription
│ ├── function.json
│ └── index.js
├── deploy
│ └── azureDeploy.json
├── docs
│ ├── RegisterApplication.md
│ └── images
│ │ ├── AAD-app-consent.png
│ │ ├── AAD-app-register.png
│ │ ├── Azure-Functions-for-Graph.svg
│ │ ├── function-create-subscription-url.png
│ │ ├── function-create-subscription.png
│ │ ├── function-process-notification-queue.png
│ │ ├── function-subscription-webhook.png
│ │ ├── function-update-subscriptions.png
│ │ └── portal-function-app.png
├── helpers
│ ├── auth.js
│ ├── config.js
│ ├── graph.js
│ ├── sourcetypes.json
│ └── splunk.js
├── host.json
├── list-subscriptions
│ ├── function.json
│ └── index.js
├── package-lock.json
├── package.json
├── process-notification-queue
│ ├── function.json
│ └── index.js
├── proxies.json
├── subscription-webhook
│ ├── function.json
│ └── index.js
├── update-subscriptions
│ ├── function.json
│ └── index.js
└── ~
│ └── .azure-functions-core-tools
│ └── Functions
│ └── ExtensionBundles
│ └── Microsoft.Azure.Functions.ExtensionBundle
│ └── 1.3.0
│ ├── StaticContent
│ └── v1
│ │ ├── bindings
│ │ └── bindings.json
│ │ ├── resources
│ │ ├── Resources.cs-CZ.json
│ │ ├── Resources.de-DE.json
│ │ ├── Resources.en-US.json
│ │ ├── Resources.es-ES.json
│ │ ├── Resources.fr-FR.json
│ │ ├── Resources.hu-HU.json
│ │ ├── Resources.it-IT.json
│ │ ├── Resources.ja-JP.json
│ │ ├── Resources.json
│ │ ├── Resources.ko-KR.json
│ │ ├── Resources.nl-NL.json
│ │ ├── Resources.pl-PL.json
│ │ ├── Resources.pt-BR.json
│ │ ├── Resources.pt-PT.json
│ │ ├── Resources.ru-RU.json
│ │ ├── Resources.sv-SE.json
│ │ ├── Resources.tr-TR.json
│ │ ├── Resources.zh-CN.json
│ │ └── Resources.zh-TW.json
│ │ └── templates
│ │ └── templates.json
│ ├── bundle.json
│ └── extensions.csproj
└── storage-hec
├── .funcignore
├── .gitignore
├── README.md
├── blob-nsg-hec
├── function.json
└── index.js
├── deploy
├── azureDeploy.json
└── azureDeploy.portal.json
├── docs
└── images
│ ├── NSG1.jpeg
│ ├── NSG2.jpeg
│ └── NSG3.jpeg
├── helpers
├── config.js
└── splunk.js
├── host.json
├── package-lock.json
├── package.json
└── proxies.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 |
24 | # nyc test coverage
25 | .nyc_output
26 |
27 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
28 | .grunt
29 |
30 | # Bower dependency directory (https://bower.io/)
31 | bower_components
32 |
33 | # node-waf configuration
34 | .lock-wscript
35 |
36 | # Compiled binary addons (https://nodejs.org/api/addons.html)
37 | build/Release
38 |
39 | # Dependency directories
40 | node_modules/
41 | jspm_packages/
42 |
43 | # TypeScript v1 declaration files
44 | typings/
45 |
46 | # Optional npm cache directory
47 | .npm
48 |
49 | # Optional eslint cache
50 | .eslintcache
51 |
52 | # Optional REPL history
53 | .node_repl_history
54 |
55 | # Output of 'npm pack'
56 | *.tgz
57 |
58 | # Yarn Integrity file
59 | .yarn-integrity
60 |
61 | # dotenv environment variables file
62 | .env
63 | .env.test
64 |
65 | # parcel-bundler cache (https://parceljs.org/)
66 | .cache
67 |
68 | # next.js build output
69 | .next
70 |
71 | # nuxt.js build output
72 | .nuxt
73 |
74 | # vuepress build output
75 | .vuepress/dist
76 |
77 | # Serverless directories
78 | .serverless/
79 |
80 | # FuseBox cache
81 | .fusebox/
82 |
83 | # DynamoDB Local files
84 | .dynamodb/
85 |
86 | # TypeScript output
87 | dist
88 | out
89 |
90 | # Azure Functions artifacts
91 | bin
92 | obj
93 | appsettings.json
94 | local.settings.json
95 |
96 | # Workstation
97 | .DS_Store
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "ms-azuretools.vscode-azurefunctions"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Attach to Node Functions",
6 | "type": "node",
7 | "request": "attach",
8 | "port": 9229,
9 | "preLaunchTask": "func: host start"
10 | }
11 | ]
12 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "azureFunctions.postDeployTask": "npm install",
3 | "azureFunctions.projectLanguage": "JavaScript",
4 | "azureFunctions.projectRuntime": "~4",
5 | "debug.internalConsoleOptions": "neverOpen",
6 | "azureFunctions.preDeployTask": "npm prune",
7 | "azureFunctions.deploySubpath": "storage-hec",
8 | "azureFunctions.projectSubpath": "storage-hec"
9 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "type": "func",
6 | "command": "host start",
7 | "problemMatcher": "$func-node-watch",
8 | "isBackground": true,
9 | "dependsOn": "npm install",
10 | "options": {
11 | "cwd": "${workspaceFolder}/storage-hec"
12 | }
13 | },
14 | {
15 | "type": "shell",
16 | "label": "npm install",
17 | "command": "npm install",
18 | "options": {
19 | "cwd": "${workspaceFolder}/storage-hec"
20 | }
21 | },
22 | {
23 | "type": "shell",
24 | "label": "npm prune",
25 | "command": "npm prune --production",
26 | "problemMatcher": [],
27 | "options": {
28 | "cwd": "${workspaceFolder}/storage-hec"
29 | }
30 | }
31 | ]
32 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 |
179 | Copyright 2020 Splunk Inc.
180 |
181 | Licensed under the Apache License, Version 2.0 (the "License");
182 | you may not use this file except in compliance with the License.
183 | You may obtain a copy of the License at
184 |
185 | http://www.apache.org/licenses/LICENSE-2.0
186 |
187 | Unless required by applicable law or agreed to in writing, software
188 | distributed under the License is distributed on an "AS IS" BASIS,
189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
190 | See the License for the specific language governing permissions and
191 | limitations under the License.
192 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Azure Functions for Splunk
2 |
3 | This repository contains available [Azure Functions]( https://azure.microsoft.com/en-us/services/functions/) to integrate Microsoft data with Splunk. Azure Functions can be triggered by certain events like an event arriving on an Event Hub, a blob written to a storage account, a Microsoft Teams call concluding, etc. The functions in this repository respond to these events and route data to Splunk accordingly.
4 |
5 | ## Getting Started
6 |
7 | Each available set of functions in this repository is contained within its own folder. For example, the Microsoft Graph functions are contained in the graph folder. To deploy the functions to your Azure environment, click the Deploy to Azure button located in the README.md in the corresponding function folder.
8 |
9 | ## Available Functions
10 |
11 | | Functions | Location | Description |
12 | | --------- | -------- | ----------- |
13 | | Microsoft Teams | [graph](https://github.com/splunk/azure-functions-splunk/tree/master/graph) | Collects [Microsoft Teams call records]( https://docs.microsoft.com/en-us/graph/api/resources/callrecords-callrecord). This data can be used with the [Microsoft 365 App for Splunk]( https://splunkbase.splunk.com/app/3786/) and/or the [RWI – Executive Dashboard]( https://splunkbase.splunk.com/app/4952/) |
14 | | Azure Event Hubs | [event-hubs-hec](https://github.com/splunk/azure-functions-splunk/tree/master/event-hubs-hec) | These Azure Functions are triggered by events arriving on an Azure Event Hub. The functions then process the events and send the event to a listening Splunk HTTP Event Collector |
15 | | Azure Storage | [storage-hec](https://github.com/splunk/azure-functions-splunk/tree/master/storage-hec) | These Azure Functions are triggered by writes to an Azure Storage account. The functions process the data to send to a listening Splunk HTTP Event Collector |
16 |
17 | ## Setting a Project Subpath
18 | [Multiple Azure Function projects](https://github.com/Microsoft/vscode-azurefunctions/wiki/Multiple-function-projects) exist in this repository. In order to debug a specific function project, set the `azureFunctions.deploySubpath` and `azureFunctions.projectSubpath` parameters in `settings.json` to the appropriate path.
19 |
20 | For example, to run and debug the `Graph` functions use the following `settings.json`
21 | ```
22 | {
23 | "azureFunctions.postDeployTask": "npm install",
24 | "azureFunctions.projectLanguage": "JavaScript",
25 | "azureFunctions.projectRuntime": "~3",
26 | "debug.internalConsoleOptions": "neverOpen",
27 | "azureFunctions.preDeployTask": "npm prune",
28 | "azureFunctions.deploySubpath": "graph",
29 | "azureFunctions.projectSubpath": "graph"
30 | }
31 | ```
32 |
33 | ## Support
34 |
35 | This software is released as-is. Splunk provides no warranty and no support on this software. If you have any issues with the software, please file an issue on the repository.
36 |
--------------------------------------------------------------------------------
/event-hubs-hec/.funcignore:
--------------------------------------------------------------------------------
1 | *.js.map
2 | *.ts
3 | .git*
4 | .vscode
5 | local.settings.json
6 | test
7 | tsconfig.json
--------------------------------------------------------------------------------
/event-hubs-hec/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 |
24 | # nyc test coverage
25 | .nyc_output
26 |
27 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
28 | .grunt
29 |
30 | # Bower dependency directory (https://bower.io/)
31 | bower_components
32 |
33 | # node-waf configuration
34 | .lock-wscript
35 |
36 | # Compiled binary addons (https://nodejs.org/api/addons.html)
37 | build/Release
38 |
39 | # Dependency directories
40 | node_modules/
41 | jspm_packages/
42 |
43 | # TypeScript v1 declaration files
44 | typings/
45 |
46 | # Optional npm cache directory
47 | .npm
48 |
49 | # Optional eslint cache
50 | .eslintcache
51 |
52 | # Optional REPL history
53 | .node_repl_history
54 |
55 | # Output of 'npm pack'
56 | *.tgz
57 |
58 | # Yarn Integrity file
59 | .yarn-integrity
60 |
61 | # dotenv environment variables file
62 | .env
63 | .env.test
64 |
65 | # parcel-bundler cache (https://parceljs.org/)
66 | .cache
67 |
68 | # next.js build output
69 | .next
70 |
71 | # nuxt.js build output
72 | .nuxt
73 |
74 | # vuepress build output
75 | .vuepress/dist
76 |
77 | # Serverless directories
78 | .serverless/
79 |
80 | # FuseBox cache
81 | .fusebox/
82 |
83 | # DynamoDB Local files
84 | .dynamodb/
85 |
86 | # TypeScript output
87 | dist
88 | out
89 |
90 | # Azure Functions artifacts
91 | bin
92 | obj
93 | appsettings.json
94 | local.settings.json
--------------------------------------------------------------------------------
/event-hubs-hec/README.md:
--------------------------------------------------------------------------------
1 | # Azure Functions for Sending Event Hub data to a Splunk HTTP Event Collector
2 | Events arriving on an Azure Event Hub can trigger serverless Azure Functions. Azure Functions can further process the raw events in near real-time.
3 |
4 |
5 |
6 |
7 |
8 | This repository contains a collection of Azure Functions for:
9 | * Processing events as they arrive on an Event Hub
10 | * Separating batched events (events in a `records[]` array) into individual events
11 | * Formatting events in the `event` format for a Splunk HTTP Event Collector
12 | * Sending event data to Splunk via [HTTP Event Collector](https://docs.splunk.com/Documentation/Splunk/latest/Data/UsetheHTTPEventCollector)
13 | * Writing event data to a Storage Blob if data cannot successfully be sent to Splunk
14 | * The [Splunk Add-on for Microsoft Cloud Services](https://splunkbase.splunk.com/app/3110/) can be utilized to retrieve Storage Blob data
15 |
16 | ## Getting Started
17 |
18 | ### 1. Create an HTTP Event Collector token in your Spunk Environment
19 | An HTTP Event Collector receives data pushed from the Azure Functions. Refer to the Splunk documentation for [setting up an HTTP Event Collector input](https://docs.splunk.com/Documentation/Splunk/latest/Data/UsetheHTTPEventCollector) in your Splunk Enterprise or Splunk Cloud environment.
20 |
21 | ### 2. Create an Event Hub Namespace
22 | An Event Hub Namespace will contain one or more Event Hubs. Refer to the Microsoft documentation for [Event Hub Namespace setup instructions](https://docs.microsoft.com/en-us/azure/event-hubs/event-hubs-create).
23 |
24 | ### 3. Send data to an Event Hub
25 | Microsoft Azure uses [diagnostics settings](https://docs.microsoft.com/en-us/azure/azure-monitor/essentials/diagnostic-settings) to define data export and destination rules. Each resource to be monitored must have a diagnostic setting. Diagnostic settings can be defined using the Azure portal, PowerShell, [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/monitor/diagnostic-settings?view=azure-cli-latest), [Resource Manager templates](https://docs.microsoft.com/en-us/azure/azure-monitor/essentials/resource-manager-diagnostic-settings), REST API, or an Azure Policy.
26 | * [Sending Azure Activity log data to an Event Hub using the Azure Portal walkthrough](docs/activity_log_diagnostic_settings.md)
27 | * [Sending Azure Active Directory logs to an Event Hub](docs/azure_ad_diagnostic_settings.md)
28 | * [Sending Azure Diagnostic logs to an Event Hub](docs/diagnostic_logs_settings.md)
29 | * [Sending Azure Metrics to an Event Hub](docs/metrics_settings.md)
30 |
31 | * Sending Azure Virtual Machine data to an Event Hub
32 | * [Windows VMs](https://docs.microsoft.com/en-us/azure/virtual-machines/extensions/diagnostics-windows)
33 | * [Linux VMs](https://docs.microsoft.com/en-us/azure/virtual-machines/extensions/diagnostics-linux)
34 |
35 |
36 | ### 4. Deploy the functions to Azure
37 |
38 | Use the "Deploy to Azure" button above to deploy the Azure Functions from this repo to your Azure account. During setup, you will be prompted for the following information:
39 |
40 | * Event Hub Namespace
41 | * Event Hub consumer group for each hub monitored
42 | * Splunk sourcetype or sourcetype base for each hub monitored
43 | * Note: see section below about sourcetypes
44 | * Splunk [HTTP Event Collector](https://docs.splunk.com/Documentation/Splunk/latest/Data/UsetheHTTPEventCollector) Endpoint
45 | * Splunk [HTTP Event Collector](https://docs.splunk.com/Documentation/Splunk/latest/Data/UsetheHTTPEventCollector) Token
46 |
47 | ## Splunk Sourcetypes
48 | ### Azure Active Directory Sourcetypes
49 | Functions that collect Azure Active Directory data use a sourcetype base. The category of the Azure Active Directory event is appended to the sourcetype base to construct the full sourcetype.
50 |
51 | **Example**
52 |
53 | The default sourcetype base for Azure Active Directory Sign-in and Audit events is `azure:aad`
54 |
55 | A sign-in event with a category of `SignInLogs` will have a sourcetype of `azure:aad:signinlogs`
56 |
57 | An audit event with a category of `AuditLogs` will have a sourcetype of `azure:aad:auditlogs`
58 |
59 | ### Diagnostic Logs
60 | Functions that collect diagnostic log data attempt to construct a sourcetype based on the `resourceId` of the event. The logic for this sourcetype construction can be found in the `getSourceType` function in the [./helpers/splunk.js file](helpers/splunk.js). The following steps are used to construct the sourcetype:
61 |
62 | * A regular expression is used to extract two groups after the text `/PROVIDERS`
63 | * Example `/PROVIDERS/`**`MICROSOFT.RESOURCES/DEPLOYMENTS/`**
64 | * Periods (`.`) and forward slashes (`/`) are replaced with colons (`:`)
65 | * The event category is appended
66 |
67 | **Example**
68 |
69 | An event with a `resourceId` of `/SUBSCRIPTIONS/subscription ID/RESOURCEGROUPS/group/PROVIDERS/MICROSOFT.RESOURCES/DEPLOYMENTS/FAILURE-ANOMALIES-ALERT-RULE-DEPLOYMENT-12345678` will have a sourcetype of `azure:resources:deployments:administrative`
70 |
71 | If a sourcetype cannot be constructed from the event, the specified default sourcetype entered at setup will be used.
72 |
73 |
74 | ## Securing Azure Function settings
75 | Microsoft stores the above values as [application settings](https://docs.microsoft.com/en-us/azure/azure-functions/functions-how-to-use-azure-function-app-settings#settings). These settings are stored encrypted, but you may opt to transfer one or more of these settings to a Key Vault. Refer to the following documentation for details on this procedure:
76 |
77 | * [Use Key Vault references for App Service and Azure Functions](https://docs.microsoft.com/en-us/azure/app-service/app-service-key-vault-references)
78 |
79 |
80 | ## Support
81 | This software is released as-is. Splunk provides no warranty and no support on this software. If you have any issues with the software, please file an issue on the repository.
--------------------------------------------------------------------------------
/event-hubs-hec/aad-logs/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "bindings": [
3 | {
4 | "type": "eventHubTrigger",
5 | "name": "eventHubMessages",
6 | "direction": "in",
7 | "eventHubName": "%AAD_LOG_HUB_NAME%",
8 | "connection": "EVENTHUB_CONNECTION_STRING",
9 | "cardinality": "many",
10 | "consumerGroup": "%AAD_LOG_CONSUMER_GROUP%",
11 | "dataType": "string"
12 | },
13 | {
14 | "name": "outputBlob",
15 | "type": "blob",
16 | "path": "undeliverable-events/%AAD_LOG_SOURCETYPE%-{rand-guid}",
17 | "connection": "BLOB_CONNECTION_STRING",
18 | "direction": "out"
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/event-hubs-hec/aad-logs/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 Splunk Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | const splunk = require('../helpers/splunk');
17 | module.exports = async function (context, eventHubMessages) {
18 |
19 | for (const event of eventHubMessages) {
20 | await splunk
21 | .sendToHEC(event, process.env["AAD_LOG_SOURCETYPE"])
22 | .catch(err => {
23 | context.log.error(`Error posting to Splunk HTTP Event Collector: ${err}`);
24 |
25 | // If the event was not successfully sent to Splunk, drop the event in a storage blob
26 | context.bindings.outputBlob = event;
27 | })
28 | }
29 | context.done();
30 | };
--------------------------------------------------------------------------------
/event-hubs-hec/aad-signin-logs-non-interactive/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "bindings": [
3 | {
4 | "type": "eventHubTrigger",
5 | "name": "eventHubMessages",
6 | "direction": "in",
7 | "eventHubName": "%AAD_NON_INTERACTIVE_SIGNIN_LOG_HUB_NAME%",
8 | "connection": "EVENTHUB_CONNECTION_STRING",
9 | "cardinality": "many",
10 | "consumerGroup": "%AAD_NON_INTERACTIVE_SIGNIN_LOG_CONSUMER_GROUP%",
11 | "dataType": "string"
12 | },
13 | {
14 | "name": "outputBlob",
15 | "type": "blob",
16 | "path": "undeliverable-events/%AAD_NON_INTERACTIVE_SIGNIN_LOG_SOURCETYPE%-{rand-guid}",
17 | "connection": "BLOB_CONNECTION_STRING",
18 | "direction": "out"
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/event-hubs-hec/aad-signin-logs-non-interactive/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 Splunk Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | const splunk = require('../helpers/splunk');
17 | module.exports = async function (context, eventHubMessages) {
18 |
19 | for (const event of eventHubMessages) {
20 | await splunk
21 | .sendToHEC(event, process.env["AAD_NON_INTERACTIVE_SIGNIN_LOG_SOURCETYPE"])
22 | .catch(err => {
23 | context.log.error(`Error posting to Splunk HTTP Event Collector: ${err}`);
24 |
25 | // If the event was not successfully sent to Splunk, drop the event in a storage blob
26 | context.bindings.outputBlob = event;
27 | })
28 | }
29 | context.done();
30 | };
--------------------------------------------------------------------------------
/event-hubs-hec/aad-signin-logs-service-principal/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "bindings": [
3 | {
4 | "type": "eventHubTrigger",
5 | "name": "eventHubMessages",
6 | "direction": "in",
7 | "eventHubName": "%AAD_SERVICE_PRINCIPAL_SIGNIN_LOG_HUB_NAME%",
8 | "connection": "EVENTHUB_CONNECTION_STRING",
9 | "cardinality": "many",
10 | "consumerGroup": "%AAD_SERVICE_PRINCIPAL_SIGNIN_LOG_CONSUMER_GROUP%",
11 | "dataType": "string"
12 | },
13 | {
14 | "name": "outputBlob",
15 | "type": "blob",
16 | "path": "undeliverable-events/%AAD_SERVICE_PRINCIPAL_SIGNIN_LOG_SOURCETYPE%-{rand-guid}",
17 | "connection": "BLOB_CONNECTION_STRING",
18 | "direction": "out"
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/event-hubs-hec/aad-signin-logs-service-principal/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 Splunk Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | const splunk = require('../helpers/splunk');
17 | module.exports = async function (context, eventHubMessages) {
18 |
19 | for (const event of eventHubMessages) {
20 | await splunk
21 | .sendToHEC(event, process.env["AAD_SERVICE_PRINCIPAL_SIGNIN_LOG_SOURCETYPE"])
22 | .catch(err => {
23 | context.log.error(`Error posting to Splunk HTTP Event Collector: ${err}`);
24 |
25 | // If the event was not successfully sent to Splunk, drop the event in a storage blob
26 | context.bindings.outputBlob = event;
27 | })
28 | }
29 | context.done();
30 | };
--------------------------------------------------------------------------------
/event-hubs-hec/activity-logs/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "bindings": [
3 | {
4 | "type": "eventHubTrigger",
5 | "name": "eventHubMessages",
6 | "direction": "in",
7 | "eventHubName": "%ACTIVITY_LOG_HUB_NAME%",
8 | "connection": "EVENTHUB_CONNECTION_STRING",
9 | "cardinality": "many",
10 | "consumerGroup": "%ACTIVITY_LOG_CONSUMER_GROUP%",
11 | "dataType": "string"
12 | },
13 | {
14 | "name": "outputBlob",
15 | "type": "blob",
16 | "path": "undeliverable-events/%ACTIVITY_LOG_SOURCETYPE%-{rand-guid}",
17 | "connection": "BLOB_CONNECTION_STRING",
18 | "direction": "out"
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/event-hubs-hec/activity-logs/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 Splunk Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | const splunk = require('../helpers/splunk');
17 | module.exports = async function (context, eventHubMessages) {
18 |
19 | for (const event of eventHubMessages) {
20 | await splunk
21 | .sendToHEC(event, process.env["ACTIVITY_LOG_SOURCETYPE"])
22 | .catch(err => {
23 | context.log.error(`Error posting to Splunk HTTP Event Collector: ${err}`);
24 |
25 | // If the event was not successfully sent to Splunk, drop the event in a storage blob
26 | context.bindings.outputBlob = event;
27 | })
28 | }
29 | context.done();
30 | };
--------------------------------------------------------------------------------
/event-hubs-hec/deploy/azureDeploy.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "appName": {
6 | "type": "string",
7 | "metadata": {
8 | "description": "The name of the function app that you wish to create."
9 | }
10 | },
11 | "storageAccountType": {
12 | "type": "string",
13 | "defaultValue": "Standard_LRS",
14 | "allowedValues": [
15 | "Standard_LRS",
16 | "Standard_GRS",
17 | "Standard_RAGRS",
18 | "Standard_ZRS"
19 | ],
20 | "metadata": {
21 | "description": "Storage Account type"
22 | }
23 | },
24 | "storageAccountTLS": {
25 | "type": "string",
26 | "defaultValue": "TLS1_2",
27 | "allowedValues": [
28 | "TLS1_0",
29 | "TLS1_1",
30 | "TLS1_2"
31 | ],
32 | "metadata": {
33 | "description": "Storage Account TLS version"
34 | }
35 | },
36 | "eventHubConnectionString": {
37 | "type": "string",
38 | "metadata": {
39 | "description": "Event Hub Namespace connection string"
40 | }
41 | },
42 |
43 | "activityLogHubName": {
44 | "type": "string",
45 | "defaultValue": "insights-activity-logs",
46 | "metadata": {
47 | "description": "Event Hub name containing activity logs"
48 | }
49 | },
50 | "activityLogEventHubConsumerGroup": {
51 | "type": "string",
52 | "defaultValue": "$Default",
53 | "metadata": {
54 | "description": "Consumer group for the activity log Event Hub"
55 | }
56 | },
57 | "activityLogSourceType": {
58 | "type": "string",
59 | "defaultValue": "azure:activity:log",
60 | "metadata": {
61 | "description": "Splunk source type for the activity log Event Hub"
62 | }
63 | },
64 | "activityLogDisabled": {
65 | "type": "bool",
66 | "defaultValue": false
67 | },
68 |
69 | "aadLogHubName": {
70 | "type": "string",
71 | "defaultValue": "insights-logs-aad",
72 | "metadata": {
73 | "description": "Event Hub name containing Azure Active Directory logs"
74 | }
75 | },
76 | "aadLogEventHubConsumerGroup": {
77 | "type": "string",
78 | "defaultValue": "$Default",
79 | "metadata": {
80 | "description": "Consumer group for the Azure Active Directory log Event Hub"
81 | }
82 | },
83 | "aadLogSourceType": {
84 | "type": "string",
85 | "defaultValue": "azure:aad",
86 | "metadata": {
87 | "description": "Splunk sourcetype base for the Azure Active Directory Sign-in and Audit log Event Hub"
88 | }
89 | },
90 | "aadLogDisabled": {
91 | "type": "bool",
92 | "defaultValue": false
93 | },
94 |
95 | "aadNoninteractiveLogHubName": {
96 | "type": "string",
97 | "defaultValue": "insights-logs-noninteractiveusersigninlogs",
98 | "metadata": {
99 | "description": "Event Hub name containing Azure Active Directory non-interactive logs"
100 | }
101 | },
102 | "aadNoninteractiveLogEventHubConsumerGroup": {
103 | "type": "string",
104 | "defaultValue": "$Default",
105 | "metadata": {
106 | "description": "Consumer group for the Azure Active Directory non-interactive log Event Hub"
107 | }
108 | },
109 | "aadNoninteractiveLogSourceType": {
110 | "type": "string",
111 | "defaultValue": "azure:aad:signin:noninteractive",
112 | "metadata": {
113 | "description": "Splunk sourcetype base for the Azure Active Directory non-interactive log Event Hub"
114 | }
115 | },
116 | "aadNoninteractiveLogDisabled": {
117 | "type": "bool",
118 | "defaultValue": true
119 | },
120 |
121 | "aadServicePrincipalLogHubName": {
122 | "type": "string",
123 | "defaultValue": "insights-logs-serviceprincipalsigninlogs",
124 | "metadata": {
125 | "description": "Event Hub name containing Azure Active Directory Service Principal logs"
126 | }
127 | },
128 | "aadServicePrincipalLogEventHubConsumerGroup": {
129 | "type": "string",
130 | "defaultValue": "$Default",
131 | "metadata": {
132 | "description": "Consumer group for the Azure Active Directory Service Principal log Event Hub"
133 | }
134 | },
135 | "aadServicePrincipalLogSourceType": {
136 | "type": "string",
137 | "defaultValue": "azure:aad:signin:serviceprincipal",
138 | "metadata": {
139 | "description": "Splunk sourcetype base for the Azure Active Directory Service Principal log Event Hub"
140 | }
141 | },
142 | "aadServicePrincipalLogDisabled": {
143 | "type": "bool",
144 | "defaultValue": true
145 | },
146 |
147 | "metricsHubName": {
148 | "type": "string",
149 | "defaultValue": "insights-metrics-pt1m",
150 | "metadata": {
151 | "description": "Event Hub name containing metrics"
152 | }
153 | },
154 | "metricsEventHubConsumerGroup": {
155 | "type": "string",
156 | "defaultValue": "$Default",
157 | "metadata": {
158 | "description": "Consumer group for the metrics Event Hub"
159 | }
160 | },
161 | "metricsSourceType": {
162 | "type": "string",
163 | "defaultValue": "azure:metrics",
164 | "metadata": {
165 | "description": "Splunk source type for the metrics Event Hub"
166 | }
167 | },
168 | "metricsDisabled": {
169 | "type": "bool",
170 | "defaultValue": true
171 | },
172 |
173 | "diagnosticsHubName": {
174 | "type": "string",
175 | "defaultValue": "insights-logs-diag",
176 | "metadata": {
177 | "description": "Event Hub name containing diagnostics"
178 | }
179 | },
180 | "diagnosticsEventHubConsumerGroup": {
181 | "type": "string",
182 | "defaultValue": "$Default",
183 | "metadata": {
184 | "description": "Consumer group for the diagnostics Event Hub"
185 | }
186 | },
187 | "diagnosticsSourceType": {
188 | "type": "string",
189 | "defaultValue": "azure:diagnostics",
190 | "metadata": {
191 | "description": "Splunk sourcetype default for the diagnostics Event Hub"
192 | }
193 | },
194 | "diagnosticsDisabled": {
195 | "type": "bool",
196 | "defaultValue": true
197 | },
198 |
199 | "githubRepoURL": {
200 | "type": "string",
201 | "defaultValue": "https://github.com/splunk/azure-functions-splunk.git"
202 | },
203 | "githubRepoBranch": {
204 | "type": "string",
205 | "defaultValue": "master"
206 | },
207 | "githubRepoProject": {
208 | "type": "string",
209 | "defaultValue": "event-hubs-hec"
210 | },
211 | "SplunkEndpoint": {
212 | "type": "string",
213 | "metadata": {
214 | "description": "Splunk HEC endpoint. For details, refer to the Splunk documentation."
215 | }
216 | },
217 | "SplunkToken": {
218 | "type": "string",
219 | "metadata": {
220 | "description": "Splunk HEC token. For details, refer to the Splunk documentation."
221 | }
222 | },
223 |
224 | "tagsByResource": {
225 | "type": "object",
226 | "defaultValue": {}
227 | }
228 | },
229 | "variables": {
230 | "functionAppName": "[parameters('appName')]",
231 | "storageAccountName": "[concat(uniquestring(resourceGroup().id), 'functions')]"
232 | },
233 | "resources": [
234 | {
235 | "type": "Microsoft.Storage/storageAccounts",
236 | "name": "[variables('storageAccountName')]",
237 | "apiVersion": "2019-06-01",
238 | "location": "[resourceGroup().location]",
239 | "tags": "[if(contains(parameters('tagsByResource'), 'Microsoft.Storage/storageAccounts'), parameters('tagsByResource')['Microsoft.Storage/storageAccounts'], json('{}'))]",
240 | "kind": "Storage",
241 | "sku": {
242 | "name": "[parameters('storageAccountType')]"
243 | },
244 | "properties": {
245 | "minimumTlsVersion": "[parameters('storageAccountTLS')]"
246 | }
247 | },
248 | {
249 | "apiVersion": "2015-05-01",
250 | "name": "[variables('functionAppName')]",
251 | "type": "Microsoft.Insights/components",
252 | "kind": "web",
253 | "location": "[resourceGroup().location]",
254 | "tags": "[if(contains(parameters('tagsByResource'), 'Microsoft.Insights/components'), parameters('tagsByResource')['Microsoft.Insights/components'], json('{}'))]",
255 | "properties": {
256 | "Application_Type": "web"
257 | }
258 | },
259 | {
260 | "apiVersion": "2019-08-01",
261 | "type": "Microsoft.Web/sites",
262 | "name": "[variables('functionAppName')]",
263 | "location": "[resourceGroup().location]",
264 | "tags": "[if(contains(parameters('tagsByResource'), 'Microsoft.Web/sites'), parameters('tagsByResource')['Microsoft.Web/sites'], json('{}'))]",
265 | "kind": "functionapp",
266 | "dependsOn": [
267 | "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"
268 | ],
269 | "properties": {
270 | "siteConfig": {
271 | "appSettings": [
272 | {
273 | "name": "AzureWebJobsStorage",
274 | "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value)]"
275 | },
276 | {
277 | "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
278 | "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value)]"
279 | },
280 | {
281 | "name": "APPINSIGHTS_INSTRUMENTATIONKEY",
282 | "value": "[reference(concat('microsoft.insights/components/', variables('functionAppName')), '2015-05-01').InstrumentationKey]"
283 | },
284 | {
285 | "name": "FUNCTIONS_WORKER_RUNTIME",
286 | "value": "node"
287 | },
288 | {
289 | "name": "WEBSITE_NODE_DEFAULT_VERSION",
290 | "value": "~18"
291 | },
292 | {
293 | "name": "FUNCTIONS_EXTENSION_VERSION",
294 | "value": "~4"
295 | },
296 | {
297 | "name": "WEBSITE_CONTENTSHARE",
298 | "value": "[toLower(variables('functionAppName'))]"
299 | },
300 | {
301 | "name": "EVENTHUB_CONNECTION_STRING",
302 | "value": "[parameters('eventHubConnectionString')]"
303 | },
304 | {
305 | "name": "ACTIVITY_LOG_HUB_NAME",
306 | "value": "[parameters('activityLogHubName')]"
307 | },
308 | {
309 | "name": "ACTIVITY_LOG_CONSUMER_GROUP",
310 | "value": "[parameters('activityLogEventHubConsumerGroup')]"
311 | },
312 | {
313 | "name": "ACTIVITY_LOG_SOURCETYPE",
314 | "value": "[parameters('activityLogSourceType')]"
315 | },
316 | {
317 | "name": "AzureWebJobs.activity-logs.Disabled",
318 | "value": "[parameters('activityLogDisabled')]"
319 | },
320 |
321 | {
322 | "name": "AAD_LOG_HUB_NAME",
323 | "value": "[parameters('aadLogHubName')]"
324 | },
325 | {
326 | "name": "AAD_LOG_CONSUMER_GROUP",
327 | "value": "[parameters('aadLogEventHubConsumerGroup')]"
328 | },
329 | {
330 | "name": "AAD_LOG_SOURCETYPE",
331 | "value": "[parameters('aadLogSourceType')]"
332 | },
333 | {
334 | "name": "AzureWebJobs.aad-logs.Disabled",
335 | "value": "[parameters('aadLogDisabled')]"
336 | },
337 |
338 | {
339 | "name": "AAD_NON_INTERACTIVE_SIGNIN_LOG_HUB_NAME",
340 | "value": "[parameters('aadNoninteractiveLogHubName')]"
341 | },
342 | {
343 | "name": "AAD_NON_INTERACTIVE_SIGNIN_LOG_CONSUMER_GROUP",
344 | "value": "[parameters('aadNoninteractiveLogEventHubConsumerGroup')]"
345 | },
346 | {
347 | "name": "AAD_NON_INTERACTIVE_SIGNIN_LOG_SOURCETYPE",
348 | "value": "[parameters('aadNoninteractiveLogSourceType')]"
349 | },
350 | {
351 | "name": "AzureWebJobs.aad-signin-logs-non-interactive.Disabled",
352 | "value": "[parameters('aadNoninteractiveLogDisabled')]"
353 | },
354 |
355 | {
356 | "name": "AAD_SERVICE_PRINCIPAL_SIGNIN_LOG_HUB_NAME",
357 | "value": "[parameters('aadServicePrincipalLogHubName')]"
358 | },
359 | {
360 | "name": "AAD_SERVICE_PRINCIPAL_SIGNIN_LOG_CONSUMER_GROUP",
361 | "value": "[parameters('aadServicePrincipalLogEventHubConsumerGroup')]"
362 | },
363 | {
364 | "name": "AAD_SERVICE_PRINCIPAL_SIGNIN_LOG_SOURCETYPE",
365 | "value": "[parameters('aadServicePrincipalLogSourceType')]"
366 | },
367 | {
368 | "name": "AzureWebJobs.aad-signin-logs-service-principal.Disabled",
369 | "value": "[parameters('aadServicePrincipalLogDisabled')]"
370 | },
371 |
372 | {
373 | "name": "METRICS_LOG_HUB_NAME",
374 | "value": "[parameters('metricsHubName')]"
375 | },
376 | {
377 | "name": "METRICS_LOG_CONSUMER_GROUP",
378 | "value": "[parameters('metricsEventHubConsumerGroup')]"
379 | },
380 | {
381 | "name": "METRICS_LOG_SOURCETYPE",
382 | "value": "[parameters('metricsSourceType')]"
383 | },
384 | {
385 | "name": "AzureWebJobs.metrics.Disabled",
386 | "value": "[parameters('metricsDisabled')]"
387 | },
388 |
389 | {
390 | "name": "DIAGNOSTIC_LOG_HUB_NAME",
391 | "value": "[parameters('diagnosticsHubName')]"
392 | },
393 | {
394 | "name": "DIAGNOSTIC_LOG_CONSUMER_GROUP",
395 | "value": "[parameters('diagnosticsEventHubConsumerGroup')]"
396 | },
397 | {
398 | "name": "DIAGNOSTIC_LOG_SOURCETYPE",
399 | "value": "[parameters('diagnosticsSourceType')]"
400 | },
401 | {
402 | "name": "AzureWebJobs.diatnostic-logs.Disabled",
403 | "value": "[parameters('diagnosticsDisabled')]"
404 | },
405 |
406 | {
407 | "name": "SPLUNK_HEC_TOKEN",
408 | "value": "[parameters('SplunkToken')]"
409 | },
410 | {
411 | "name": "SPLUNK_HEC_URL",
412 | "value": "[parameters('SplunkEndpoint')]"
413 | },
414 | {
415 | "name": "BLOB_CONNECTION_STRING",
416 | "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value)]"
417 | },
418 | {
419 | "name": "Project",
420 | "value": "[parameters('githubRepoProject')]"
421 | }
422 | ]
423 | }
424 | },
425 | "resources": [
426 | {
427 | "apiVersion": "2016-08-01",
428 | "name": "web",
429 | "type": "sourcecontrols",
430 | "dependsOn": [
431 | "[resourceId('Microsoft.Web/Sites', variables('functionAppName'))]"
432 | ],
433 | "properties": {
434 | "RepoUrl": "[parameters('githubRepoURL')]",
435 | "branch": "[parameters('githubRepoBranch')]",
436 | "IsManualIntegration": true
437 | }
438 | }
439 | ]
440 | }
441 | ]
442 | }
443 |
--------------------------------------------------------------------------------
/event-hubs-hec/deploy/azureDeploy.portal.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/0.1.2-preview/CreateUIDefinition.MultiVm.json#",
3 | "handler": "Microsoft.Azure.CreateUIDef",
4 | "version": "0.1.2-preview",
5 | "parameters": {
6 | "config": {
7 | "isWizard": true
8 | },
9 | "basics": [
10 | {
11 | "name": "appName",
12 | "label": "Function App Name",
13 | "type": "Microsoft.Common.TextBox",
14 | "toolTip": "The name of the function app that you wish to create.",
15 | "visible": true,
16 | "constraints": {
17 | "required": true
18 | }
19 | },
20 | {
21 | "name": "storageAccountType",
22 | "label": "Storage Account Type",
23 | "type": "Microsoft.Common.DropDown",
24 | "toolTip": "The storage account will contain a blob container for undeliverable events.",
25 | "constraints": {
26 | "required": true,
27 | "allowedValues": [
28 | {
29 | "label": "Standard Locally Redundant Storage",
30 | "value": "Standard_LRS"
31 | },
32 | {
33 | "label": "Standard Geo Replicated Storage",
34 | "value": "Standard_GRS"
35 | },
36 | {
37 | "label": "Standard Read-Access Geo Replicated Storage",
38 | "value": "Standard_RAGRS"
39 | },
40 | {
41 | "label": "Standard Zone Redundant Storage",
42 | "value": "Standard_ZRS"
43 | }
44 | ]
45 | }
46 | },
47 | {
48 | "name": "storageAccountTLS",
49 | "label": "Storage Account Minimum TLS Version",
50 | "type": "Microsoft.Common.DropDown",
51 | "toolTip": "Minimum TLS Version",
52 | "constraints": {
53 | "required": true,
54 | "allowedValues": [
55 | {
56 | "label": "TLS 1.0",
57 | "value": "TLS1_0"
58 | },
59 | {
60 | "label": "TLS 1.1",
61 | "value": "TLS1_1"
62 | },
63 | {
64 | "label": "TLS 1.2",
65 | "value": "TLS1_2"
66 | }
67 | ]
68 | }
69 | }
70 | ],
71 | "steps": [
72 | {
73 | "name": "appSettings",
74 | "label": "Function App Settings",
75 | "elements": [
76 | {
77 | "name": "ehNsSection",
78 | "type": "Microsoft.Common.Section",
79 | "label": "Event Hub Namespace Details",
80 | "elements": []
81 | },
82 | {
83 | "name": "ehNamespaceApi",
84 | "type": "Microsoft.Solutions.ArmApiControl",
85 | "request": {
86 | "method": "GET",
87 | "path": "[concat(subscription().id, '/providers/Microsoft.EventHub/namespaces', '?api-version=2017-04-01')]"
88 | }
89 | },
90 | {
91 | "name": "ehNamespaceDropDown",
92 | "type": "Microsoft.Common.DropDown",
93 | "label": "Event Hub Namespace",
94 | "toolTip": "Event Hub Namespace containing the Event Hubs to monitor.",
95 | "filter": true,
96 | "constraints": {
97 | "allowedValues": "[map(steps('appSettings').ehNamespaceApi.value, (item) => parse(concat('{\"label\":\"', item.name, '\",\"value\":\"', item.id, '\"}')))]",
98 | "required": true
99 | },
100 | "visible": true
101 | },
102 | {
103 | "name": "ehPolicyApi",
104 | "type": "Microsoft.Solutions.ArmApiControl",
105 | "request": {
106 | "method": "GET",
107 | "path": "[concat(steps('appSettings').ehNamespaceDropDown, '/authorizationRules', '?api-version=2017-04-01')]"
108 | }
109 | },
110 | {
111 | "name": "ehPolicyDropDown",
112 | "type": "Microsoft.Common.DropDown",
113 | "label": "Event Hub Access Policy",
114 | "toolTip": "Specify a policy with a 'Listen' or greater claim.",
115 | "constraints": {
116 | "allowedValues": "[map(steps('appSettings').ehPolicyApi.value, (item) => parse(concat('{\"label\":\"', item.name, '\",\"value\":\"', item.name, '\"}')))]",
117 | "required": true
118 | },
119 | "visible": true
120 | },
121 | {
122 | "name": "ehPolicyKeyApi",
123 | "type": "Microsoft.Solutions.ArmApiControl",
124 | "request": {
125 | "method": "POST",
126 | "path": "[concat(steps('appSettings').ehNamespaceDropDown, '/authorizationRules/', steps('appSettings').ehPolicyDropDown, '/listKeys', '?api-version=2017-04-01')]"
127 | }
128 | },
129 | {
130 | "name": "ehApi",
131 | "type": "Microsoft.Solutions.ArmApiControl",
132 | "request": {
133 | "method": "GET",
134 | "path": "[concat(steps('appSettings').ehNamespaceDropDown, '/eventhubs', '?api-version=2017-04-01')]"
135 | }
136 | },
137 | {
138 | "name": "splunkHECSection",
139 | "type": "Microsoft.Common.Section",
140 | "label": "Splunk HTTP Event Collector Details",
141 | "elements": [
142 | {
143 | "name": "splunkHECEndpoint",
144 | "label": "Splunk HEC Endpoint",
145 | "toolTip": "URL for the Splunk HTTP Event Collector.",
146 | "type": "Microsoft.Common.TextBox",
147 | "placeholder": "https://:/services/collector/event",
148 | "constraints": {
149 | "required": true
150 | }
151 | },
152 | {
153 | "name": "splunkHECToken",
154 | "label": "Splunk HEC Token",
155 | "toolTip": "Splunk HTTP Event Collector Token.",
156 | "type": "Microsoft.Common.TextBox",
157 | "constraints": {
158 | "required": true
159 | }
160 | }
161 | ]
162 | },
163 |
164 | {
165 | "name": "repoSection",
166 | "type": "Microsoft.Common.Section",
167 | "label": "Function Repository",
168 | "elements": [
169 | {
170 | "name": "repoURL",
171 | "label": "Repository URL",
172 | "toolTip": "URL for the function code.",
173 | "type": "Microsoft.Common.TextBox",
174 | "defaultValue": "https://github.com/splunk/azure-functions-splunk.git",
175 | "constraints": {
176 | "required": true
177 | }
178 | },
179 | {
180 | "name": "repoBranch",
181 | "label": "Branch",
182 | "defaultValue": "master",
183 | "type": "Microsoft.Common.TextBox",
184 | "constraints": {
185 | "required": true
186 | }
187 | },
188 | {
189 | "name": "repoProject",
190 | "label": "Project",
191 | "toolTip": "If the source code for the function resides in a subdirectory, specify the subdirectory containing the code.",
192 | "type": "Microsoft.Common.TextBox",
193 | "defaultValue": "event-hubs-hec"
194 | }
195 | ]
196 | }
197 | ]
198 | },
199 | {
200 | "name": "ehActivityLogs",
201 | "label": "Activity Logs",
202 | "elements": [
203 | {
204 | "name": "ehActivityEnable",
205 | "type": "Microsoft.Common.CheckBox",
206 | "label": "Enable Activity Log Event Hub Functions"
207 | },
208 | {
209 | "name": "ehActivityDropDown",
210 | "type": "Microsoft.Common.DropDown",
211 | "label": "Activity Log Event Hub",
212 | "toolTip": "Event Hub containing activity data.",
213 | "defaultValue": "insights-activity-logs",
214 | "constraints": {
215 | "allowedValues": "[map(steps('appSettings').ehApi.value, (item) => parse(concat('{\"label\":\"', item.name, '\",\"value\":\"', item.name, '\"}')))]",
216 | "required": "[steps('ehActivityLogs').ehActivityEnable]"
217 | },
218 | "visible": "[steps('ehActivityLogs').ehActivityEnable]"
219 | },
220 | {
221 | "name": "ehActivityConsumerGroupApi",
222 | "type": "Microsoft.Solutions.ArmApiControl",
223 | "request": {
224 | "method": "GET",
225 | "path": "[concat(steps('appSettings').ehNamespaceDropDown, '/eventhubs/', steps('ehActivityLogs').ehActivityDropDown, '/consumergroups', '?api-version=2017-04-01')]"
226 | }
227 | },
228 | {
229 | "name": "ehActivityConsumerGroupDropDown",
230 | "type": "Microsoft.Common.DropDown",
231 | "label": "Activity Log Consumer Group",
232 | "constraints": {
233 | "allowedValues": "[map(steps('ehActivityLogs').ehActivityConsumerGroupApi.value, (item) => parse(concat('{\"label\":\"', item.name, '\",\"value\":\"', item.name, '\"}')))]",
234 | "required": "[steps('ehActivityLogs').ehActivityEnable]"
235 | },
236 | "visible": "[steps('ehActivityLogs').ehActivityEnable]"
237 | },
238 | {
239 | "name": "ehActivitySourcetype",
240 | "label": "Activity Log Sourcetype",
241 | "toolTip": "Splunk sourcetype for activity logs.",
242 | "type": "Microsoft.Common.TextBox",
243 | "defaultValue": "azure:activity:log",
244 | "placeholder": "azure:activity:log",
245 | "constraints": {
246 | "required": "[steps('ehActivityLogs').ehActivityEnable]"
247 | },
248 | "visible": "[steps('ehActivityLogs').ehActivityEnable]"
249 | }
250 | ]
251 | },
252 | {
253 | "name": "ehAADLogs",
254 | "label": "Azure Active Directory",
255 | "elements": [
256 | {
257 | "name": "ehAADSASection",
258 | "type": "Microsoft.Common.Section",
259 | "label": "Sign-In and Audit Logs",
260 | "elements": [
261 | {
262 | "name": "ehAADSAEnable",
263 | "type": "Microsoft.Common.CheckBox",
264 | "label": "Enable AAD Sign and Audit Event Hub Functions"
265 | },
266 | {
267 | "name": "ehAADSADropDown",
268 | "type": "Microsoft.Common.DropDown",
269 | "label": "AAD Sign-in and Audit Event Hub",
270 | "toolTip": "Event Hub containing Azure AD sign-in and audit data.",
271 | "defaultValue": "insights-activity-logs",
272 | "constraints": {
273 | "allowedValues": "[map(steps('appSettings').ehApi.value, (item) => parse(concat('{\"label\":\"', item.name, '\",\"value\":\"', item.name, '\"}')))]",
274 | "required": "[steps('ehAADLogs').ehAADSASection.ehAADSAEnable]"
275 | },
276 | "visible": "[steps('ehAADLogs').ehAADSASection.ehAADSAEnable]"
277 | },
278 | {
279 | "name": "ehAADSAConsumerGroupApi",
280 | "type": "Microsoft.Solutions.ArmApiControl",
281 | "request": {
282 | "method": "GET",
283 | "path": "[concat(steps('appSettings').ehNamespaceDropDown, '/eventhubs/', steps('ehAADLogs').ehAADSASection.ehAADSADropDown, '/consumergroups', '?api-version=2017-04-01')]"
284 | }
285 | },
286 | {
287 | "name": "ehAADSAConsumerGroupDropDown",
288 | "type": "Microsoft.Common.DropDown",
289 | "label": "AAD Sign-in and Audit Consumer Group",
290 | "constraints": {
291 | "allowedValues": "[map(steps('ehAADLogs').ehAADSASection.ehAADSAConsumerGroupApi.value, (item) => parse(concat('{\"label\":\"', item.name, '\",\"value\":\"', item.name, '\"}')))]",
292 | "required": "[steps('ehAADLogs').ehAADSASection.ehAADSAEnable]"
293 | },
294 | "visible": "[steps('ehAADLogs').ehAADSASection.ehAADSAEnable]"
295 | },
296 | {
297 | "name": "ehAADSASourcetype",
298 | "label": "AAD Sign-in and Audit Sourcetype Base",
299 | "toolTip": "Splunk sourcetype base for Azure AD sign-in and activity data.",
300 | "type": "Microsoft.Common.TextBox",
301 | "defaultValue": "azure:aad",
302 | "placeholder": "azure:aad",
303 | "constraints": {
304 | "required": "[steps('ehAADLogs').ehAADSASection.ehAADSAEnable]"
305 | },
306 | "visible": "[steps('ehAADLogs').ehAADSASection.ehAADSAEnable]"
307 | },
308 | {
309 | "name": "ehAADSASourcetypeInfo",
310 | "type": "Microsoft.Common.InfoBox",
311 | "visible": "[steps('ehAADLogs').ehAADSASection.ehAADSAEnable]",
312 | "options": {
313 | "icon": "Info",
314 | "text": "The base sourcetype will be combined with the event category to construct the full Splunk sourcetype.",
315 | "uri": "https://github.com/splunk/azure-functions-splunk/tree/event-hubs/event-hubs-hec#splunk-sourcetypes"
316 | }
317 | }
318 | ]
319 | },
320 | {
321 | "name": "ehAADNISection",
322 | "type": "Microsoft.Common.Section",
323 | "label": "Non-interactive User Sign-In Logs",
324 | "elements": [
325 | {
326 | "name": "ehAADNIEnable",
327 | "type": "Microsoft.Common.CheckBox",
328 | "label": "Enable AAD Non-interactive User Sign-In Log Functions"
329 | },
330 | {
331 | "name": "ehAADNIDropDown",
332 | "type": "Microsoft.Common.DropDown",
333 | "label": "AAD Non-interactive User Sign-In Event Hub",
334 | "toolTip": "Event Hub containing Azure AD non-interactive user sign-in data.",
335 | "defaultValue": "insights-logs-noninteractiveusersigninlogs",
336 | "constraints": {
337 | "allowedValues": "[map(steps('appSettings').ehApi.value, (item) => parse(concat('{\"label\":\"', item.name, '\",\"value\":\"', item.name, '\"}')))]",
338 | "required": "[steps('ehAADLogs').ehAADNISection.ehAADNIEnable]"
339 | },
340 | "visible": "[steps('ehAADLogs').ehAADNISection.ehAADNIEnable]"
341 | },
342 | {
343 | "name": "ehAADNIConsumerGroupApi",
344 | "type": "Microsoft.Solutions.ArmApiControl",
345 | "request": {
346 | "method": "GET",
347 | "path": "[concat(steps('appSettings').ehNamespaceDropDown, '/eventhubs/', steps('ehAADLogs').ehAADNISection.ehAADNIDropDown, '/consumergroups', '?api-version=2017-04-01')]"
348 | }
349 | },
350 | {
351 | "name": "ehAADNIConsumerGroupDropDown",
352 | "type": "Microsoft.Common.DropDown",
353 | "label": "AAD Non-interactive User Sign-In Consumer Group",
354 | "constraints": {
355 | "allowedValues": "[map(steps('ehAADLogs').ehAADNISection.ehAADNIConsumerGroupApi.value, (item) => parse(concat('{\"label\":\"', item.name, '\",\"value\":\"', item.name, '\"}')))]",
356 | "required": "[steps('ehAADLogs').ehAADNISection.ehAADNIEnable]"
357 | },
358 | "visible": "[steps('ehAADLogs').ehAADNISection.ehAADNIEnable]"
359 | },
360 | {
361 | "name": "ehAADNISourcetype",
362 | "label": "AAD Non-interactive User Sign-In Sourcetype",
363 | "toolTip": "Splunk sourcetype for Azure AD non-interactive user sign-in data.",
364 | "type": "Microsoft.Common.TextBox",
365 | "defaultValue": "azure:aad:signin:noninteractive",
366 | "placeholder": "azure:aad:signin:noninteractive",
367 | "constraints": {
368 | "required": "[steps('ehAADLogs').ehAADNISection.ehAADNIEnable]"
369 | },
370 | "visible": "[steps('ehAADLogs').ehAADNISection.ehAADNIEnable]"
371 | },
372 | {
373 | "name": "ehAADNISourcetypeInfo",
374 | "type": "Microsoft.Common.InfoBox",
375 | "visible": "[steps('ehAADLogs').ehAADNISection.ehAADNIEnable]",
376 | "options": {
377 | "icon": "Info",
378 | "text": "The base sourcetype will be combined with the event category to construct the full Splunk sourcetype.",
379 | "uri": "https://github.com/splunk/azure-functions-splunk/tree/event-hubs/event-hubs-hec#splunk-sourcetypes"
380 | }
381 | }
382 | ]
383 | },
384 | {
385 | "name": "ehAADSPSection",
386 | "type": "Microsoft.Common.Section",
387 | "label": "Service Principal Sign-In Logs",
388 | "elements": [
389 | {
390 | "name": "ehAADSPEnable",
391 | "type": "Microsoft.Common.CheckBox",
392 | "label": "Enable AAD Service Principal Sign-In Log Functions"
393 | },
394 | {
395 | "name": "ehAADSPDropDown",
396 | "type": "Microsoft.Common.DropDown",
397 | "label": "AAD Service Principal Sign-In Event Hub",
398 | "toolTip": "Event Hub containing Azure AD Service Principal sign-in data.",
399 | "defaultValue": "insights-logs-serviceprincipalsigninlogs",
400 | "constraints": {
401 | "allowedValues": "[map(steps('appSettings').ehApi.value, (item) => parse(concat('{\"label\":\"', item.name, '\",\"value\":\"', item.name, '\"}')))]",
402 | "required": "[steps('ehAADLogs').ehAADSPSection.ehAADSPEnable]"
403 | },
404 | "visible": "[steps('ehAADLogs').ehAADSPSection.ehAADSPEnable]"
405 | },
406 | {
407 | "name": "ehAADSPConsumerGroupApi",
408 | "type": "Microsoft.Solutions.ArmApiControl",
409 | "request": {
410 | "method": "GET",
411 | "path": "[concat(steps('appSettings').ehNamespaceDropDown, '/eventhubs/', steps('ehAADLogs').ehAADSPSection.ehAADSPDropDown, '/consumergroups', '?api-version=2017-04-01')]"
412 | }
413 | },
414 | {
415 | "name": "ehAADSPConsumerGroupDropDown",
416 | "type": "Microsoft.Common.DropDown",
417 | "label": "AAD Service Principal Sign-In Consumer Group",
418 | "constraints": {
419 | "allowedValues": "[map(steps('ehAADLogs').ehAADSPSection.ehAADSPConsumerGroupApi.value, (item) => parse(concat('{\"label\":\"', item.name, '\",\"value\":\"', item.name, '\"}')))]",
420 | "required": "[steps('ehAADLogs').ehAADSPSection.ehAADSPEnable]"
421 | },
422 | "visible": "[steps('ehAADLogs').ehAADSPSection.ehAADSPEnable]"
423 | },
424 | {
425 | "name": "ehAADSPSourcetype",
426 | "label": "AAD Service Principal Sign-In Sourcetype",
427 | "toolTip": "Splunk sourcetype for Azure AD service principal sign-in data.",
428 | "type": "Microsoft.Common.TextBox",
429 | "defaultValue": "azure:aad:signin:serviceprincipal",
430 | "placeholder": "azure:aad:signin:serviceprincipal",
431 | "constraints": {
432 | "required": "[steps('ehAADLogs').ehAADSPSection.ehAADSPEnable]"
433 | },
434 | "visible": "[steps('ehAADLogs').ehAADSPSection.ehAADSPEnable]"
435 | },
436 | {
437 | "name": "ehAADSPSourcetypeInfo",
438 | "type": "Microsoft.Common.InfoBox",
439 | "visible": "[steps('ehAADLogs').ehAADSPSection.ehAADSPEnable]",
440 | "options": {
441 | "icon": "Info",
442 | "text": "The base sourcetype will be combined with the event category to construct the full Splunk sourcetype.",
443 | "uri": "https://github.com/splunk/azure-functions-splunk/tree/event-hubs/event-hubs-hec#splunk-sourcetypes"
444 | }
445 | }
446 | ]
447 | }
448 | ]
449 | },
450 | {
451 | "name": "ehDiagnosticsLogs",
452 | "label": "Diagnostics",
453 | "elements": [
454 | {
455 | "name": "ehDiagnosticsEnable",
456 | "type": "Microsoft.Common.CheckBox",
457 | "label": "Enable Diagnostic Log Event Hub Functions"
458 | },
459 | {
460 | "name": "ehDiagnosticsDropDown",
461 | "type": "Microsoft.Common.DropDown",
462 | "label": "Diagnostics Log Event Hub",
463 | "toolTip": "Event Hub containing diagnostics data.",
464 | "defaultValue": "insights-logs-diag",
465 | "constraints": {
466 | "allowedValues": "[map(steps('appSettings').ehApi.value, (item) => parse(concat('{\"label\":\"', item.name, '\",\"value\":\"', item.name, '\"}')))]",
467 | "required": "[steps('ehDiagnosticsLogs').ehDiagnosticsEnable]"
468 | },
469 | "visible": "[steps('ehDiagnosticsLogs').ehDiagnosticsEnable]"
470 | },
471 | {
472 | "name": "ehDiagnosticsConsumerGroupApi",
473 | "type": "Microsoft.Solutions.ArmApiControl",
474 | "request": {
475 | "method": "GET",
476 | "path": "[concat(steps('appSettings').ehNamespaceDropDown, '/eventhubs/', steps('ehDiagnosticsLogs').ehDiagnosticsDropDown, '/consumergroups', '?api-version=2017-04-01')]"
477 | }
478 | },
479 | {
480 | "name": "ehDiagnosticsConsumerGroupDropDown",
481 | "type": "Microsoft.Common.DropDown",
482 | "label": "Diagnostics Log Consumer Group",
483 | "constraints": {
484 | "allowedValues": "[map(steps('ehDiagnosticsLogs').ehDiagnosticsConsumerGroupApi.value, (item) => parse(concat('{\"label\":\"', item.name, '\",\"value\":\"', item.name, '\"}')))]",
485 | "required": "[steps('ehDiagnosticsLogs').ehDiagnosticsEnable]"
486 | },
487 | "visible": "[steps('ehDiagnosticsLogs').ehDiagnosticsEnable]"
488 | },
489 | {
490 | "name": "ehDiagnosticsSourcetype",
491 | "label": "Diagnostics Log Default Sourcetype",
492 | "toolTip": "Splunk default sourcetype for diagnostics logs.",
493 | "type": "Microsoft.Common.TextBox",
494 | "defaultValue": "azure:diagnostics",
495 | "placeholder": "azure:diagnostics",
496 | "constraints": {
497 | "required": "[steps('ehDiagnosticsLogs').ehDiagnosticsEnable]"
498 | },
499 | "visible": "[steps('ehDiagnosticsLogs').ehDiagnosticsEnable]"
500 | },
501 | {
502 | "name": "ehDiagnosticsSourcetypeInfo",
503 | "type": "Microsoft.Common.InfoBox",
504 | "visible": "[steps('ehDiagnosticsLogs').ehDiagnosticsEnable]",
505 | "options": {
506 | "icon": "Info",
507 | "text": "The function code will attempt to create a Splunk sourcetype based on the resourceId of the event. If a sourcetype cannot be constructed from the event, the specified default sourcetype entered will be used.",
508 | "uri": "https://github.com/splunk/azure-functions-splunk/tree/event-hubs/event-hubs-hec#splunk-sourcetypes"
509 | }
510 | }
511 | ]
512 | },
513 | {
514 | "name": "ehMetrics",
515 | "label": "Metrics",
516 | "elements": [
517 | {
518 | "name": "ehMetricsEnable",
519 | "type": "Microsoft.Common.CheckBox",
520 | "label": "Enable Metrics Event Hub Functions"
521 | },
522 | {
523 | "name": "ehMetricsDropDown",
524 | "type": "Microsoft.Common.DropDown",
525 | "label": "Metrics Event Hub",
526 | "toolTip": "Event Hub containing metric data.",
527 | "defaultValue": "insights-metrics-pt1m",
528 | "constraints": {
529 | "allowedValues": "[map(steps('appSettings').ehApi.value, (item) => parse(concat('{\"label\":\"', item.name, '\",\"value\":\"', item.name, '\"}')))]",
530 | "required": "[steps('ehMetrics').ehMetricsEnable]"
531 | },
532 | "visible": "[steps('ehMetrics').ehMetricsEnable]"
533 | },
534 | {
535 | "name": "ehMetricsConsumerGroupApi",
536 | "type": "Microsoft.Solutions.ArmApiControl",
537 | "request": {
538 | "method": "GET",
539 | "path": "[concat(steps('appSettings').ehNamespaceDropDown, '/eventhubs/', steps('ehMetrics').ehMetricsDropDown, '/consumergroups', '?api-version=2017-04-01')]"
540 | }
541 | },
542 | {
543 | "name": "ehMetricsConsumerGroupDropDown",
544 | "type": "Microsoft.Common.DropDown",
545 | "label": "Metrics Consumer Group",
546 | "constraints": {
547 | "allowedValues": "[map(steps('ehMetrics').ehMetricsConsumerGroupApi.value, (item) => parse(concat('{\"label\":\"', item.name, '\",\"value\":\"', item.name, '\"}')))]",
548 | "required": "[steps('ehMetrics').ehMetricsEnable]"
549 | },
550 | "visible": "[steps('ehMetrics').ehMetricsEnable]"
551 | },
552 | {
553 | "name": "ehMetricsSourcetype",
554 | "label": "Metrics Sourcetype",
555 | "toolTip": "Splunk sourcetype for metrics.",
556 | "type": "Microsoft.Common.TextBox",
557 | "defaultValue": "azure:metrics",
558 | "placeholder": "azure:metrics",
559 | "constraints": {
560 | "required": "[steps('ehMetrics').ehMetricsEnable]"
561 | },
562 | "visible": "[steps('ehMetrics').ehMetricsEnable]"
563 | }
564 | ]
565 | },
566 | {
567 | "name": "tags",
568 | "label": "Tags",
569 | "elements": [
570 | {
571 | "name": "tagsByResource",
572 | "type": "Microsoft.Common.TagsByResource",
573 | "resources": [
574 | "Microsoft.Storage/storageAccounts",
575 | "Microsoft.Insights/components",
576 | "Microsoft.Web/sites"
577 | ]
578 | }
579 | ]
580 | }
581 | ],
582 | "outputs": {
583 | "storageAccountType": "[steps('basics').storageAccountType]",
584 | "storageAccountTLS": "[steps('basics').storageAccountTLS]",
585 | "appName": "[steps('basics').appName]",
586 |
587 | "eventHubConnectionString": "[steps('appSettings').ehPolicyKeyApi.primaryConnectionString]",
588 | "SplunkEndpoint": "[steps('appSettings').splunkHECSection.splunkHECEndpoint]",
589 | "SplunkToken": "[steps('appSettings').splunkHECSection.splunkHECToken]",
590 | "githubRepoURL": "[steps('appSettings').repoSection.repoURL]",
591 | "githubRepoBranch": "[steps('appSettings').repoSection.repoBranch]",
592 | "githubRepoProject": "[steps('appSettings').repoSection.repoProject]",
593 |
594 | "activityLogHubName": "[steps('ehActivityLogs').ehActivityDropDown]",
595 | "activityLogEventHubConsumerGroup": "[steps('ehActivityLogs').ehActivityConsumerGroupDropDown]",
596 | "activityLogSourceType": "[steps('ehActivityLogs').ehActivitySourcetype]",
597 | "activityLogDisabled": "[if(steps('ehActivityLogs').ehActivityEnable, false, true)]",
598 |
599 | "aadLogHubName": "[steps('ehAADLogs').ehAADSASection.ehAADSADropDown]",
600 | "aadLogEventHubConsumerGroup": "[steps('ehAADLogs').ehAADSASection.ehAADSAConsumerGroupDropDown]",
601 | "aadLogSourceType": "[steps('ehAADLogs').ehAADSASection.ehAADSASourcetype]",
602 | "aadLogDisabled": "[if(steps('ehAADLogs').ehAADSASection.ehAADSAEnable, false, true)]",
603 |
604 | "aadNoninteractiveLogHubName": "[steps('ehAADLogs').ehAADNISection.ehAADNIDropDown]",
605 | "aadNoninteractiveLogEventHubConsumerGroup": "[steps('ehAADLogs').ehAADNISection.ehAADNIConsumerGroupDropDown]",
606 | "aadNoninteractiveLogSourceType": "[steps('ehAADLogs').ehAADNISection.ehAADNISourcetype]",
607 | "aadNoninteractiveLogDisabled": "[if(steps('ehAADLogs').ehAADNISection.ehAADNIEnable, false, true)]",
608 |
609 | "aadServicePrincipalLogHubName": "[steps('ehAADLogs').ehAADSPSection.ehAADSPDropDown]",
610 | "aadServicePrincipalLogEventHubConsumerGroup": "[steps('ehAADLogs').ehAADSPSection.ehAADSPConsumerGroupDropDown]",
611 | "aadServicePrincipalLogSourceType": "[steps('ehAADLogs').ehAADSPSection.ehAADSPSourcetype]",
612 | "aadServicePrincipalLogDisabled": "[if(steps('ehAADLogs').ehAADSPSection.ehAADSPEnable, false, true)]",
613 |
614 | "diagnosticsHubName": "[steps('ehDiagnosticsLogs').ehDiagnosticsDropDown]",
615 | "diagnosticsEventHubConsumerGroup": "[steps('ehDiagnosticsLogs').ehDiagnosticsConsumerGroupDropDown]",
616 | "diagnosticsSourceType": "[steps('ehDiagnosticsLogs').ehDiagnosticsSourcetype]",
617 | "diagnosticsDisabled": "[if(steps('ehDiagnosticsLogs').ehDiagnosticsEnable, false, true)]",
618 |
619 | "metricsHubName": "[steps('ehMetrics').ehMetricsDropDown]",
620 | "metricsEventHubConsumerGroup": "[steps('ehMetrics').ehMetricsConsumerGroupDropDown]",
621 | "metricsSourceType": "[steps('ehMetrics').ehMetricsSourcetype]",
622 | "metricsDisabled": "[if(steps('ehMetrics').ehMetricsEnable, false, true)]",
623 |
624 | "tagsByResource": "[steps('tags').tagsByResource]"
625 | }
626 | }
627 | }
--------------------------------------------------------------------------------
/event-hubs-hec/diagnostic-logs/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "bindings": [
3 | {
4 | "type": "eventHubTrigger",
5 | "name": "eventHubMessages",
6 | "direction": "in",
7 | "eventHubName": "%DIAGNOSTIC_LOG_HUB_NAME%",
8 | "connection": "EVENTHUB_CONNECTION_STRING",
9 | "cardinality": "many",
10 | "consumerGroup": "%DIAGNOSTIC_LOG_CONSUMER_GROUP%",
11 | "dataType": "string"
12 | },
13 | {
14 | "name": "outputBlob",
15 | "type": "blob",
16 | "path": "undeliverable-events/%DIAGNOSTIC_LOG_HUB_NAME%-{rand-guid}",
17 | "connection": "BLOB_CONNECTION_STRING",
18 | "direction": "out"
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/event-hubs-hec/diagnostic-logs/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 Splunk Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | const splunk = require('../helpers/splunk');
17 | module.exports = async function (context, eventHubMessages) {
18 |
19 | for (const event of eventHubMessages) {
20 | await splunk
21 | .sendToHEC(event, process.env["DIAGNOSTIC_LOG_SOURCETYPE"])
22 | .catch(err => {
23 | context.log.error(`Error posting to Splunk HTTP Event Collector: ${err}`);
24 |
25 | // If the event was not successfully sent to Splunk, drop the event in a storage blob
26 | context.bindings.outputBlob = event;
27 | })
28 | }
29 | context.done();
30 | };
--------------------------------------------------------------------------------
/event-hubs-hec/docs/activity_log_diagnostic_settings.md:
--------------------------------------------------------------------------------
1 | # Sending Azure Activity log data to an Event Hub
2 |
3 | ## Create a Event Hub for Activity Logs
4 | * From the Azure Portal, select Event Hubs
5 | * Select your Event Hub Namespace
6 | * Select Event Hubs
7 | * Click the **+ Event Hub** button to create a new Event Hub
8 | * Name the hub `insights-activity-logs`
9 | * Note: `insights-activity-logs` is the default for the functions, but a different name may be used. If using a different event hub name, update the function application settings to reflect the correct event hub name.
10 | * Set the number of partitions
11 | * Note: at least 4 partitions are recommended
12 | * Click the **Create** button
13 |
14 | ## Use a Diagnostic Setting to Send Logs to the Event Hub
15 | * From the Azure Portal, select the [Activity Log](https://portal.azure.com/#blade/Microsoft_Azure_Monitoring/AzureMonitoringBrowseBlade/activityLog)
16 | * Select **Diagnostics settings**
17 | * Choose an existing setting or create a new setting
18 | * Give the diagnostic setting a name
19 | * Select the events to log
20 | * Select "Stream to an event hub"
21 | * Select your event hub details
22 | * Click the Save button
23 |
24 | [](images/Activity_Logs_Diagnostic_Settings.png)
25 |
26 | [](images/Activity_Logs_Event_Hub.png)
--------------------------------------------------------------------------------
/event-hubs-hec/docs/azure_ad_diagnostic_settings.md:
--------------------------------------------------------------------------------
1 | # Sending Azure Active Directory log data to an Event Hub
2 |
3 | ## Create a common Azure Active Directory Event Hub
4 | * From the Azure Portal, select Event Hubs
5 | * Select your Event Hub Namespace
6 | * Select Event Hubs
7 | * Click the **+ Event Hub** button to create a new Event Hub
8 | * Name the hub `insights-logs-aad`
9 | * Note: `insights-logs-aad` is the default for the functions, but a different name may be used. If using a different event hub name, update the function application settings to reflect the correct event hub name.
10 | * Set the number of partitions
11 | * Note: at least 4 partitions are recommended
12 | * Click the **Create** button
13 |
14 | ## Use a Diagnostic Setting to Send Logs to the Event Hub
15 | * From the Azure Portal, select Azure Active Directory
16 | * In the **Monitoring** section, select **Diagnostic settings**
17 | * Choose an existing setting or create a new setting
18 | * Give the diagnostic setting a name
19 | * Select the events to log
20 | * Note: `NonInteractiveUserSignInLogs` and `ServicePrincipalSignInLogs` are higher volume data sources. The recommended practice is to use a separate diagnostic setting to send these logs to a separate event hub. Specific functions are contained in this repository for `NonInteractiveUserSignInLogs` and `ServicePrincipalSignInLogs`
21 | * Select "Stream to an event hub"
22 | * Select your event hub details
23 | * ***Important:*** select the event hub created in the step above (`insights-logs-aad` by default)
24 | * Click the Save button
25 |
26 | [](images/AAD_Event_Hub.png)
--------------------------------------------------------------------------------
/event-hubs-hec/docs/diagnostic_logs_settings.md:
--------------------------------------------------------------------------------
1 | # Sending Diagnostics log data to an Event Hub
2 |
3 | ## Create a common Diagnostics Event Hub
4 | * From the Azure Portal, select Event Hubs
5 | * Select your Event Hub Namespace
6 | * Select Event Hubs
7 | * Click the **+ Event Hub** button to create a new Event Hub
8 | * Name the hub `insights-logs-diag`
9 | * Note: `insights-logs-diag` is the default for the functions, but a different name may be used. If using a different event hub name, update the function application settings to reflect the correct event hub name.
10 | * Set the number of partitions
11 | * Note: at least 4 partitions are recommended
12 | * Click the **Create** button
13 |
14 | ## Use a Diagnostic Setting to Send Logs to the Event Hub
15 | * From the Azure Portal, select the resource you want to enable for diagnostic log settings
16 | * In the **Monitoring** section, select **Diagnostic settings**
17 | * Choose an existing setting or create a new setting
18 | * Give the diagnostic setting a name
19 | * Select the events to log
20 | * Select "Stream to an event hub"
21 | * Select your event hub details
22 | * ***Important:*** select the event hub created in the step above (`insights-logs-diag` by default)
23 | * Click the Save button
--------------------------------------------------------------------------------
/event-hubs-hec/docs/images/AAD_Event_Hub.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/splunk/azure-functions-splunk/a96cb5f504192d24d26b2daf003c7d9b2b3b471e/event-hubs-hec/docs/images/AAD_Event_Hub.png
--------------------------------------------------------------------------------
/event-hubs-hec/docs/images/Activity_Logs_Diagnostic_Settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/splunk/azure-functions-splunk/a96cb5f504192d24d26b2daf003c7d9b2b3b471e/event-hubs-hec/docs/images/Activity_Logs_Diagnostic_Settings.png
--------------------------------------------------------------------------------
/event-hubs-hec/docs/metrics_settings.md:
--------------------------------------------------------------------------------
1 | # Sending Metrics data to an Event Hub
2 |
3 | ## Create a common Metrics Event Hub
4 | * From the Azure Portal, select Event Hubs
5 | * Select your Event Hub Namespace
6 | * Select Event Hubs
7 | * Click the **+ Event Hub** button to create a new Event Hub
8 | * Name the hub `insights-metrics-pt1m`
9 | * Note: `insights-metrics-pt1m` is the default for the functions, but a different name may be used. If using a different event hub name, update the function application settings to reflect the correct event hub name.
10 | * Set the number of partitions
11 | * Note: at least 4 partitions are recommended
12 | * Click the **Create** button
13 |
14 | ## Use a Diagnostic Setting to Send Logs to the Event Hub
15 | * From the Azure Portal, select the resource you want to enable for diagnostic log settings
16 | * In the **Monitoring** section, select **Diagnostic settings**
17 | * Choose an existing setting or create a new setting
18 | * Give the diagnostic setting a name
19 | * Select the events to log
20 | * Select "Stream to an event hub"
21 | * Select your event hub details
22 | * ***Important:*** select the event hub created in the step above (`insights-metrics-pt1m` by default)
23 | * Click the Save button
--------------------------------------------------------------------------------
/event-hubs-hec/helpers/config.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 Splunk Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | const checkConfig = function () {
18 | let splunkHecUrl = process.env["SPLUNK_HEC_URL"];
19 | let splunkHecToken = process.env["SPLUNK_HEC_TOKEN"];
20 |
21 | if (!splunkHecUrl || !splunkHecToken) {
22 | return false;
23 | }
24 |
25 | return true;
26 | }
27 |
28 | exports.checkConfig = checkConfig;
--------------------------------------------------------------------------------
/event-hubs-hec/helpers/splunk.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 Splunk Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | const axios = require('axios');
17 |
18 | const getSourceType = function(sourcetype, resourceId, category) {
19 |
20 | // If this is an AAD sourcetype, append the category to the sourcetype and return
21 | let aadSourcetypes = [process.env["AAD_LOG_SOURCETYPE"], process.env["AAD_NON_INTERACTIVE_SIGNIN_LOG_SOURCETYPE"], process.env["AAD_SERVICE_PRINCIPAL_SIGNIN_LOG_SOURCETYPE"], process.env["AAD_PROVISIONING_LOG_SOURCETYPE"]];
22 | if(aadSourcetypes.indexOf(sourcetype) > -1) {
23 | return `${sourcetype}:${category.toLowerCase()}`;
24 | }
25 |
26 | // Set the sourcetype based on the resourceId
27 | let sourcetypePattern = /PROVIDERS\/(.*?\/.*?)(?:\/)/;
28 | try {
29 | let st = resourceId.match(sourcetypePattern)[1]
30 | .replace("MICROSOFT.", "azure:")
31 | .replace('.', ':')
32 | .replace('/', ':')
33 | .toLowerCase();
34 | return `${st}:${category.toLowerCase()}`;
35 | } catch(err) {
36 | // Could not detrmine the sourcetype from the resourceId
37 | return sourcetype;
38 | }
39 | }
40 |
41 | const getEpochTime = function(timeString) {
42 | try {
43 | let epochTime = new Date(timeString).getTime();
44 | return epochTime;
45 | } catch {
46 | return null;
47 | }
48 | }
49 |
50 | const getTimeStamp = function(message) {
51 | if(message.hasOwnProperty('time')) {
52 | return getEpochTime(message["time"]);
53 | }
54 | return null;
55 | }
56 |
57 | const getHECPayload = async function(message, sourcetype) {
58 |
59 | try {
60 | jsonMessage = JSON.parse(message);
61 | } catch (err) {
62 | // The message is not JSON, so send it as-is.
63 | let payload = {
64 | "sourcetype": sourcetype,
65 | "event": message
66 | }
67 | return payload;
68 | }
69 |
70 | // If the JSON contains a records[] array, batch the events for HEC.
71 | if(jsonMessage.hasOwnProperty('records')) {
72 | let payload = ''
73 |
74 | jsonMessage.records.forEach(function(record) {
75 |
76 | let recordEvent = {
77 | "sourcetype": sourcetype,
78 | "event": JSON.stringify(record)
79 | }
80 |
81 | if((record.hasOwnProperty('resourceId')) && (record.hasOwnProperty('category'))) {
82 | // Get the sourcetype
83 | recordEvent["sourcetype"] = getSourceType(sourcetype, record.resourceId, record.category);
84 | }
85 |
86 | let eventTimeStamp = getTimeStamp(record);
87 | if(eventTimeStamp) { recordEvent["time"] = eventTimeStamp; }
88 | payload += JSON.stringify(recordEvent);
89 | });
90 | return payload
91 | }
92 |
93 | // If we made it here, the JSON does not contain a records[] array, so send the data as-is
94 | let payload = {
95 | "sourcetype": sourcetype,
96 | "event": JSON.stringify(jsonMessage)
97 | }
98 | let eventTimeStamp = getTimeStamp(jsonMessage);
99 | if(eventTimeStamp) { payload["time"] = eventTimeStamp; }
100 | return payload
101 | }
102 |
103 | const sendToHEC = async function(message, sourcetype) {
104 |
105 | let headers = {
106 | "Authorization": `Splunk ${process.env["SPLUNK_HEC_TOKEN"]}`
107 | }
108 |
109 | await getHECPayload(message, sourcetype)
110 | .then(payload => {
111 | return axios.post(process.env["SPLUNK_HEC_URL"], payload, {headers: headers});
112 | })
113 | .catch(err => {
114 | throw err;
115 | });
116 | }
117 |
118 | exports.sendToHEC = sendToHEC;
--------------------------------------------------------------------------------
/event-hubs-hec/host.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0",
3 | "logging": {
4 | "applicationInsights": {
5 | "samplingSettings": {
6 | "isEnabled": true,
7 | "excludedTypes": "Request"
8 | }
9 | }
10 | },
11 | "extensionBundle": {
12 | "id": "Microsoft.Azure.Functions.ExtensionBundle",
13 | "version": "[2.*, 3.0.0)"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/event-hubs-hec/metrics/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "bindings": [
3 | {
4 | "type": "eventHubTrigger",
5 | "name": "eventHubMessages",
6 | "direction": "in",
7 | "eventHubName": "%METRICS_LOG_HUB_NAME%",
8 | "connection": "EVENTHUB_CONNECTION_STRING",
9 | "cardinality": "many",
10 | "consumerGroup": "%METRICS_LOG_CONSUMER_GROUP%",
11 | "dataType": "string"
12 | },
13 | {
14 | "name": "outputBlob",
15 | "type": "blob",
16 | "path": "undeliverable-events/%METRICS_LOG_SOURCETYPE%-{rand-guid}",
17 | "connection": "BLOB_CONNECTION_STRING",
18 | "direction": "out"
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/event-hubs-hec/metrics/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 Splunk Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | const splunk = require('../helpers/splunk');
17 | module.exports = async function (context, eventHubMessages) {
18 |
19 | for (const event of eventHubMessages) {
20 | await splunk
21 | .sendToHEC(event, process.env["METRICS_LOG_SOURCETYPE"])
22 | .catch(err => {
23 | context.log.error(`Error posting to Splunk HTTP Event Collector: ${err}`);
24 |
25 | // If the event was not successfully sent to Splunk, drop the event in a storage blob
26 | context.bindings.outputBlob = event;
27 | })
28 | }
29 | context.done();
30 | };
--------------------------------------------------------------------------------
/event-hubs-hec/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "event-hubs-hec",
3 | "version": "1.0.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "asynckit": {
8 | "version": "0.4.0",
9 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
10 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
11 | },
12 | "axios": {
13 | "version": "1.6.0",
14 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz",
15 | "integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==",
16 | "requires": {
17 | "follow-redirects": "^1.15.0",
18 | "form-data": "^4.0.0",
19 | "proxy-from-env": "^1.1.0"
20 | }
21 | },
22 | "combined-stream": {
23 | "version": "1.0.8",
24 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
25 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
26 | "requires": {
27 | "delayed-stream": "~1.0.0"
28 | }
29 | },
30 | "delayed-stream": {
31 | "version": "1.0.0",
32 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
33 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
34 | },
35 | "follow-redirects": {
36 | "version": "1.15.6",
37 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
38 | "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA=="
39 | },
40 | "form-data": {
41 | "version": "4.0.0",
42 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
43 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
44 | "requires": {
45 | "asynckit": "^0.4.0",
46 | "combined-stream": "^1.0.8",
47 | "mime-types": "^2.1.12"
48 | }
49 | },
50 | "mime-db": {
51 | "version": "1.52.0",
52 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
53 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
54 | },
55 | "mime-types": {
56 | "version": "2.1.35",
57 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
58 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
59 | "requires": {
60 | "mime-db": "1.52.0"
61 | }
62 | },
63 | "proxy-from-env": {
64 | "version": "1.1.0",
65 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
66 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/event-hubs-hec/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "event-hubs-hec",
3 | "version": "1.0.0",
4 | "description": "Sends Event Hub events to Splunk's HTTP Event Collector",
5 | "scripts": {
6 | "start": "func start",
7 | "test": "echo \"No tests yet...\""
8 | },
9 | "dependencies": {
10 | "axios": ">=1.6.0"
11 | },
12 | "devDependencies": {}
13 | }
14 |
--------------------------------------------------------------------------------
/event-hubs-hec/proxies.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/proxies",
3 | "proxies": {}
4 | }
5 |
--------------------------------------------------------------------------------
/graph/.funcignore:
--------------------------------------------------------------------------------
1 | *.js.map
2 | *.ts
3 | .git*
4 | .vscode
5 | local.settings.json
6 | test
7 | tsconfig.json
--------------------------------------------------------------------------------
/graph/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 |
179 | Copyright 2020 Splunk Inc.
180 |
181 | Licensed under the Apache License, Version 2.0 (the "License");
182 | you may not use this file except in compliance with the License.
183 | You may obtain a copy of the License at
184 |
185 | http://www.apache.org/licenses/LICENSE-2.0
186 |
187 | Unless required by applicable law or agreed to in writing, software
188 | distributed under the License is distributed on an "AS IS" BASIS,
189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
190 | See the License for the specific language governing permissions and
191 | limitations under the License.
192 |
--------------------------------------------------------------------------------
/graph/README.md:
--------------------------------------------------------------------------------
1 | # Azure Functions for Splunk and Microsoft Graph
2 |
3 | > Microsoft Teams call record data is currently the only supported source for these functions
4 |
5 | A collection of Azure Functions for:
6 | * Managing Microsoft Graph notification [subscriptions](https://docs.microsoft.com/en-us/graph/api/resources/subscription)
7 | * Retrieving data about notifications received from Microsoft Graph subscriptions
8 | * Sending Microsoft Graph resource data to Splunk via [HTTP Event Collector](https://docs.splunk.com/Documentation/Splunk/latest/Data/UsetheHTTPEventCollector)
9 |
10 | ## Getting Started
11 |
12 | ### 1. Register your application
13 | When you register an application with Azure AD, you are creating an identity configuration. The Azure Functions in this project will use the application for authentication when interacting with the Microsoft Graph API.
14 |
15 | #### Additional information
16 | * [Registering an Azure AD application](docs/RegisterApplication.md) provides a detailed walkthrough
17 | * [Application and service principal objects in Azure Active Directory](https://docs.microsoft.com/en-us/azure/active-directory/develop/app-objects-and-service-principals)
18 |
19 | ### 2. Deploy the functions to Azure
20 |
21 | [](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fsplunk%2Fazure-functions-splunk%2Fmaster%2Fgraph%2Fdeploy%2FazureDeploy.json)
22 |
23 | Use the above button to deploy the Azure Functions from this repo to your Azure account. During setup, you will be prompted for the following information:
24 |
25 | * Client ID
26 | * Client Secret
27 | * Splunk [HTTP Event Collector](https://docs.splunk.com/Documentation/Splunk/latest/Data/UsetheHTTPEventCollector) Endpoint
28 | * Splunk [HTTP Event Collector](https://docs.splunk.com/Documentation/Splunk/latest/Data/UsetheHTTPEventCollector) Token
29 |
30 | #### Securing Azure Function settings
31 | Microsoft stores the above values as [application settings](https://docs.microsoft.com/en-us/azure/azure-functions/functions-how-to-use-azure-function-app-settings#settings). These settings are stored encrypted, but you may opt to transfer one or more of these settings to a Key Vault. Refer to the following documentation for details on this procedure:
32 |
33 | * [Use Key Vault references for App Service and Azure Functions](https://docs.microsoft.com/en-us/azure/app-service/app-service-key-vault-references)
34 |
35 | ### 3. Create a Microsoft Graph subscription
36 |
37 | A Microsoft Graph subscription defines where Microsoft should deliver notifications. To create a subscription, execute the `create-subscription` function from the Azure Portal.
38 |
39 | * Launch the [Azure Portal](https://portal.azure.com)
40 | * Navigate to the Function app section in the portal
41 | * Select the Function app created previously
42 | * Click Functions -> create-subscription
43 | * Note: only [callRecords](https://docs.microsoft.com/en-us/graph/api/resources/callrecords-callrecord) subscriptions are created in this release.
44 |
45 | [](docs/images/portal-function-app.png)
46 |
47 | * In the Overview section, click Get Function Url
48 | * Copy the URL and paste it in a new browser tab
49 |
50 | [](docs/images/function-create-subscription-url.png)
51 |
52 | > Note: you may receive a timeout when executing this function for the first time. In this event, refresh your browser. The reason for this is the `create-subscription` function makes a call to the `subscription-webhook` function which may not be running yet.
53 |
54 | * To list subscriptions, execute the `list-subscriptions` function.
55 | * To delete a subscription, copy the subscription's ID field and pass it as a query parameter named `subscriptionId` to the `delete-subscription` function.
56 | * Example: `https://FUNCTION-APP.azurewebsites.net/api/delete-subscription?code=CODE&subscriptionId=SUBSCRIPTION_ID`
57 |
58 | ### 4. View data in Splunk
59 |
60 | Run the following Splunk query
61 |
62 | > `sourcetype="m365:*"`
63 |
64 | (optional) Download and install the [Microsoft 365 App for Splunk](https://splunkbase.splunk.com/app/3786/)
65 |
66 | ## Support
67 | This software is released as-is. Splunk provides no warranty and no support on this software. If you have any issues with the software, please file an issue on the repository.
68 |
69 | ## How it works
70 | 1. When the `create-subscription` function successfully creates a Microsoft Graph subscription, the subscription ID and expiration date is written to a storage blob.
71 | 1. After a subscribed event occurs, a notification is sent to the `subscription-webhook`. The `subscription-webhook` commits the data to a notification queue to keep things speedy.
72 | 1. When an event arrives on the notification queue, the `process-notification-queue` function is triggered. This function retrieves the data from Microsoft Graph and forwards the data to Splunk.
73 | 1. Since subscriptions have a short lifespan, the `update-subscriptions` function periodically reads the blobs and will update subscriptions if they are about to expire.
74 |
75 | [](docs/images/Azure-Functions-for-Graph.svg)
76 |
--------------------------------------------------------------------------------
/graph/create-subscription/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "bindings": [
3 | {
4 | "authLevel": "function",
5 | "type": "httpTrigger",
6 | "direction": "in",
7 | "name": "req",
8 | "methods": [
9 | "get",
10 | "post"
11 | ]
12 | },
13 | {
14 | "type": "http",
15 | "direction": "out",
16 | "name": "res"
17 | }
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/graph/create-subscription/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 Splunk Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | const { BlobServiceClient } = require("@azure/storage-blob");
18 | const blobServiceClient = BlobServiceClient.fromConnectionString(process.env["AzureWebJobsStorage"]);
19 | const containerClient = blobServiceClient.getContainerClient('subscriptions');
20 | const graph = require('../helpers/graph');
21 | const splunk = require('../helpers/splunk');
22 |
23 | module.exports = async function (context, req) {
24 | let message = '[create-subscription] function triggered';
25 | context.log(message);
26 | splunk.logInfo(message);
27 |
28 | // Currently only supporting call records
29 | let resource = "/communications/callRecords";
30 |
31 | // Create a Date 2 days in the future for the exipiration time.
32 | let expirationDateTime = new Date();
33 | expirationDateTime.setDate(expirationDateTime.getDate() + 2);
34 | let subscriptionBody = {
35 | "changeType":"created, updated",
36 | "notificationUrl": req.originalUrl.replace("create-subscription", "subscription-webhook"),
37 | "resource": resource,
38 | "expirationDateTime": expirationDateTime.toISOString()
39 | }
40 |
41 | await containerClient.createIfNotExists()
42 | .catch((err) => {
43 | context.log.error(err);
44 | splunk.logError(`[create-subscription] ${JSON.stringify(err)}`);
45 | context.res = {
46 | body: `Error creating subscription blob container: ${JSON.stringify(err, null, 4)}`
47 | };
48 | return;
49 | });
50 |
51 | await graph.createSubscription(subscriptionBody)
52 | .then((subscription) => {
53 | msg = `[create-subscription] created subscription: ${JSON.stringify(subscription, null, 4)}`
54 | context.log(msg);
55 | splunk.logInfo(msg);
56 | return subscription;
57 | })
58 | .then((subscription) => {
59 | // Persist the subscription details to a blob.
60 | // A timer function will update the subscription expirationDateTime if needed.
61 |
62 | subscriptionBlobItem = {
63 | "subscriptionId": subscription.id,
64 | "subscriptionExpirationDateTime": subscription.expirationDateTime
65 | }
66 |
67 | let blobContent = JSON.stringify(subscriptionBlobItem);
68 | let blobName = subscription.id;
69 | let blockBlobClient = containerClient.getBlockBlobClient(blobName);
70 | blockBlobClient
71 | .upload(blobContent, Buffer.byteLength(blobContent))
72 | .catch((err) => {
73 | throw new Error(`The subscription was created, but there was an error writing the subscription contents to a blob container. Details: ${err}`)
74 | });
75 | let msg = `[create-subscription] successfully created subscription: ${JSON.stringify(subscription, null, 4)}`
76 | context.log(msg);
77 | splunk.logInfo(msg);
78 | context.res = {
79 | body: msg
80 | };
81 | })
82 | .catch((err) => {
83 | context.log.error(err);
84 | splunk.logError(JSON.stringify(err));
85 | context.res = {
86 | body: `[create-subscription] error: ${JSON.stringify(err, null, 4)}`
87 | };
88 | return;
89 | });
90 | };
--------------------------------------------------------------------------------
/graph/delete-subscription/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "bindings": [
3 | {
4 | "authLevel": "function",
5 | "type": "httpTrigger",
6 | "direction": "in",
7 | "name": "req",
8 | "methods": [
9 | "get",
10 | "post"
11 | ]
12 | },
13 | {
14 | "type": "http",
15 | "direction": "out",
16 | "name": "res"
17 | }
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/graph/delete-subscription/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 Splunk Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | const { BlobServiceClient } = require("@azure/storage-blob");
18 | const blobServiceClient = BlobServiceClient.fromConnectionString(process.env["AzureWebJobsStorage"]);
19 | const containerClient = blobServiceClient.getContainerClient('subscriptions');
20 | const graph = require('../helpers/graph');
21 | const splunk = require('../helpers/splunk');
22 |
23 | module.exports = async function (context, req) {
24 | let msg = '[delete-subscription] function triggered';
25 | context.log(msg);
26 | splunk.logInfo(msg);
27 |
28 | if (req.query.subscriptionId || (req.body && req.body.subscriptionId)) {
29 | subscriptionId = (req.query.subscriptionId || req.body.subscriptionId);
30 |
31 | await graph.deleteSubscription(subscriptionId)
32 | .then(() => {
33 | msg = `[delete-subscription] deleted subscription with ID ${subscriptionId}`
34 | context.log(msg);
35 | splunk.logInfo(msg);
36 | containerClient.deleteBlob(subscriptionId);
37 | context.res = {
38 | body: msg
39 | }
40 | })
41 | .catch((err) => {
42 | context.log.error(err);
43 | splunk.logError(`[delete-subscription] ${JSON.stringify(err)}`);
44 | context.res = {
45 | body: `Error: ${JSON.stringify(err, null, 4)}`
46 | };
47 | });
48 | }
49 | else {
50 | context.res = {
51 | status: 400,
52 | body: "Please pass a subscriptionId on the query string or in the request body."
53 | };
54 | }
55 | };
--------------------------------------------------------------------------------
/graph/deploy/azureDeploy.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "appName": {
6 | "type": "string",
7 | "metadata": {
8 | "description": "The name of the function app that you wish to create."
9 | }
10 | },
11 | "storageAccountType": {
12 | "type": "string",
13 | "defaultValue": "Standard_LRS",
14 | "allowedValues": [
15 | "Standard_LRS",
16 | "Standard_GRS",
17 | "Standard_RAGRS"
18 | ],
19 | "metadata": {
20 | "description": "Storage Account type"
21 | }
22 | },
23 | "githubRepoURL": {
24 | "type": "string",
25 | "defaultValue": "https://github.com/splunk/azure-functions-splunk.git"
26 | },
27 | "githubRepoBranch": {
28 | "type": "string",
29 | "defaultValue": "master"
30 | },
31 | "SplunkEndpoint": {
32 | "type": "string",
33 | "metadata": {
34 | "description": "Splunk HEC endpoint. For details, refer to the Splunk documentation."
35 | }
36 | },
37 | "SplunkToken": {
38 | "type": "string",
39 | "metadata": {
40 | "description": "Splunk HEC token. For details, refer to the Splunk documentation."
41 | }
42 | },
43 | "ClientID" : {
44 | "type": "string",
45 | "metadata": {
46 | "description": "Documentation"
47 | }
48 | },
49 | "ClientSecret": {
50 | "type": "string",
51 | "metadata": {
52 | "description": "Documentation"
53 | }
54 | }
55 | },
56 | "variables": {
57 | "functionAppName": "[parameters('appName')]",
58 | "storageAccountName": "[concat(uniquestring(resourceGroup().id), 'functions')]"
59 | },
60 | "resources": [
61 | {
62 | "type": "Microsoft.Storage/storageAccounts",
63 | "name": "[variables('storageAccountName')]",
64 | "apiVersion": "2019-06-01",
65 | "location": "[resourceGroup().location]",
66 | "kind": "Storage",
67 | "sku": {
68 | "name": "[parameters('storageAccountType')]"
69 | }
70 | },
71 | {
72 | "apiVersion": "2015-05-01",
73 | "name": "[variables('functionAppName')]",
74 | "type": "Microsoft.Insights/components",
75 | "kind": "web",
76 | "location": "[resourceGroup().location]",
77 | "properties": {
78 | "Application_Type": "web"
79 | }
80 | },
81 | {
82 | "apiVersion": "2019-08-01",
83 | "type": "Microsoft.Web/sites",
84 | "name": "[variables('functionAppName')]",
85 | "location": "[resourceGroup().location]",
86 | "kind": "functionapp",
87 | "dependsOn": [
88 | "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"
89 | ],
90 | "properties": {
91 | "siteConfig": {
92 | "appSettings": [
93 | {
94 | "name": "AzureWebJobsStorage",
95 | "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value)]"
96 | },
97 | {
98 | "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
99 | "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value)]"
100 | },
101 | {
102 | "name": "APPINSIGHTS_INSTRUMENTATIONKEY",
103 | "value": "[reference(concat('microsoft.insights/components/', variables('functionAppName')), '2015-05-01').InstrumentationKey]"
104 | },
105 | {
106 | "name": "FUNCTIONS_WORKER_RUNTIME",
107 | "value": "node"
108 | },
109 | {
110 | "name": "WEBSITE_NODE_DEFAULT_VERSION",
111 | "value": "~18"
112 | },
113 | {
114 | "name": "FUNCTIONS_EXTENSION_VERSION",
115 | "value": "~4"
116 | },
117 | {
118 | "name": "WEBSITE_CONTENTSHARE",
119 | "value": "[toLower(variables('functionAppName'))]"
120 | },
121 | {
122 | "name": "SPLUNK_HEC_TOKEN",
123 | "value": "[parameters('SplunkToken')]"
124 | },
125 | {
126 | "name": "SPLUNK_HEC_URL",
127 | "value": "[parameters('SplunkEndpoint')]"
128 | },
129 | {
130 | "name": "CLIENT_ID",
131 | "value": "[parameters('ClientID')]"
132 | },
133 | {
134 | "name": "CLIENT_SECRET",
135 | "value": "[parameters('ClientSecret')]"
136 | },
137 | {
138 | "name": "TENANT_ID",
139 | "value": "[subscription().tenantId]"
140 | },
141 | {
142 | "name": "Project",
143 | "value": "graph"
144 | }
145 | ]
146 | }
147 | },
148 | "resources": [
149 | {
150 | "apiVersion": "2016-08-01",
151 | "name": "web",
152 | "type": "sourcecontrols",
153 | "dependsOn": [
154 | "[resourceId('Microsoft.Web/Sites', variables('functionAppName'))]"
155 | ],
156 | "properties": {
157 | "RepoUrl": "[parameters('githubRepoURL')]",
158 | "branch": "[parameters('githubRepoBranch')]",
159 | "IsManualIntegration": true
160 | }
161 | }
162 | ]
163 | }
164 | ]
165 | }
166 |
--------------------------------------------------------------------------------
/graph/docs/RegisterApplication.md:
--------------------------------------------------------------------------------
1 | # Registering an Azure AD application
2 |
3 | When you register an application with Azure AD, you are creating an identity configuration. The Azure Functions in this project will use the application for authentication when interacting with the Microsoft Graph API.
4 |
5 | ## Prerequisites
6 |
7 | * An Azure account with an active subscription. [Create an account for free](https://azure.microsoft.com/free/).
8 | * An [Azure AD tenant](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-create-new-tenant).
9 |
10 | ## Register a new application using the Azure portal
11 |
12 | 1. Sign in to the [Azure portal](https://portal.azure.com)
13 | 1. If your account gives you access to more than one tenant, select your account in the upper right corner. Set your portal session to the Azure AD tenant that you want.
14 | 1. Search for and select **Azure Active Directory**. Under **Manage**, select **App registrations**.
15 | 1. Select **New registration**.
16 | 1. In **Register an application**, enter a meaningful application name.
17 | 1. For account type, select **Accounts in this organizational directory only**.
18 | 1. **Redirect URI** can be left blank.
19 | 1. When finished, select **Register**.
20 |
21 | [](images/AAD-app-register.png)
22 |
23 |
24 | ## Add API permissions to the application
25 |
26 | 1. Select **API permissions**
27 | 1. Select **Add a permission**
28 | 1. Select **Microsoft Graph**
29 | 1. Add appropriate permissions (refer to the section below for required permissions based on resource type).
30 | 1. When finished, select **Add permissions**
31 | 1. Some permissions require admin consent. For example, in the screenshot below, `CallRecords.Read.All` requires admin consent.
32 | 1. If the selected permission requires admin consent, select **Grant admin consent for...**
33 |
34 | [](images/AAD-app-consent.png)
35 |
36 |
37 | ## Application permissions needed for data collection
38 |
39 | | Resource Type | Required Permissions |
40 | | ------------- | -------------------- |
41 | | Call Records | `CallRecords.Read.All` (Application) |
42 |
43 |
44 | ## Resources
45 |
46 | * [Microsoft Graph permissions reference](https://docs.microsoft.com/en-us/graph/permissions-reference)
--------------------------------------------------------------------------------
/graph/docs/images/AAD-app-consent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/splunk/azure-functions-splunk/a96cb5f504192d24d26b2daf003c7d9b2b3b471e/graph/docs/images/AAD-app-consent.png
--------------------------------------------------------------------------------
/graph/docs/images/AAD-app-register.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/splunk/azure-functions-splunk/a96cb5f504192d24d26b2daf003c7d9b2b3b471e/graph/docs/images/AAD-app-register.png
--------------------------------------------------------------------------------
/graph/docs/images/function-create-subscription-url.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/splunk/azure-functions-splunk/a96cb5f504192d24d26b2daf003c7d9b2b3b471e/graph/docs/images/function-create-subscription-url.png
--------------------------------------------------------------------------------
/graph/docs/images/function-create-subscription.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/splunk/azure-functions-splunk/a96cb5f504192d24d26b2daf003c7d9b2b3b471e/graph/docs/images/function-create-subscription.png
--------------------------------------------------------------------------------
/graph/docs/images/function-process-notification-queue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/splunk/azure-functions-splunk/a96cb5f504192d24d26b2daf003c7d9b2b3b471e/graph/docs/images/function-process-notification-queue.png
--------------------------------------------------------------------------------
/graph/docs/images/function-subscription-webhook.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/splunk/azure-functions-splunk/a96cb5f504192d24d26b2daf003c7d9b2b3b471e/graph/docs/images/function-subscription-webhook.png
--------------------------------------------------------------------------------
/graph/docs/images/function-update-subscriptions.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/splunk/azure-functions-splunk/a96cb5f504192d24d26b2daf003c7d9b2b3b471e/graph/docs/images/function-update-subscriptions.png
--------------------------------------------------------------------------------
/graph/docs/images/portal-function-app.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/splunk/azure-functions-splunk/a96cb5f504192d24d26b2daf003c7d9b2b3b471e/graph/docs/images/portal-function-app.png
--------------------------------------------------------------------------------
/graph/helpers/auth.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 Splunk Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | const adal = require('adal-node');
18 | const clientId = process.env["CLIENT_ID"];
19 | const clientSecret = process.env["CLIENT_SECRET"];
20 | const tenantId = process.env["TENANT_ID"];
21 | const authority = `https://login.microsoftonline.com/${tenantId}`
22 | const resource = 'https://graph.microsoft.com/';
23 |
24 | const getAccessToken = function () {
25 | const authContext = new adal.AuthenticationContext(authority);
26 | return new Promise((resolve, reject) => {
27 | authContext.acquireTokenWithClientCredentials(resource, clientId, clientSecret, (err, token) => {
28 | if (err) {
29 | reject(err);
30 | } else {
31 | resolve(token["accessToken"]);
32 | }
33 | });
34 | });
35 | }
36 |
37 | exports.getAccessToken = getAccessToken;
--------------------------------------------------------------------------------
/graph/helpers/config.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 Splunk Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | const checkConfig = function () {
18 | let tenantId = process.env["TENANT_ID"];
19 | let clientId = process.env["CLIENT_ID"];
20 | let clientSecret = process.env["CLIENT_SECRET"];
21 | let splunkHecUrl = process.env["SPLUNK_HEC_URL"];
22 | let splunkHecToken = process.env["SPLUNK_HEC_TOKEN"];
23 |
24 | if (!tenantId || !clientId || !clientSecret || !splunkHecUrl || !splunkHecToken) {
25 | return false;
26 | }
27 |
28 | return true;
29 | }
30 |
31 | exports.checkConfig = checkConfig;
--------------------------------------------------------------------------------
/graph/helpers/graph.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 Splunk Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | const auth = require('./auth');
18 | const utils = require('./config');
19 | const MicrosoftGraph = require('@microsoft/microsoft-graph-client');
20 | require("isomorphic-fetch");
21 |
22 | const graphClient = MicrosoftGraph.Client.init({
23 | authProvider: (done) => {
24 | done(null, auth.getAccessToken());
25 | }
26 | });
27 |
28 | const createSubscription = async function(subscriptionBody) {
29 |
30 | return await graphClient.api('/subscriptions')
31 | .post(subscriptionBody)
32 | .then((subscription) => {
33 | return subscription;
34 | })
35 | .catch ((err) => {
36 | throw err;
37 | });
38 | }
39 |
40 | const listSubscriptions = async function() {
41 |
42 | return await graphClient.api(`/subscriptions/`)
43 | .get()
44 | .catch((err) => {
45 | throw err;
46 | });
47 | }
48 |
49 | const updateSubscriptionExpiration = async function(subscriptionId, expirationDateTime) {
50 |
51 | return await graphClient.api(`/subscriptions/${subscriptionId}`)
52 | .update({
53 | "expirationDateTime" : expirationDateTime
54 | })
55 | .catch((err) => {
56 | throw err;
57 | });
58 | }
59 |
60 | const deleteSubscription = async function(subscriptionId) {
61 |
62 | return await graphClient.api(`/subscriptions/${subscriptionId}`)
63 | .delete()
64 | .catch((err) => {
65 | throw err;
66 | });
67 |
68 | }
69 |
70 | const getResource = async function(resource) {
71 |
72 | if (resource.startsWith("communications/callRecords")) {
73 | // We need to expand sessions and segments for call records
74 | return await graphClient.api(resource)
75 | .expand("sessions($expand=segments)")
76 | .get()
77 | .catch((err) => {
78 | throw err;
79 | });
80 |
81 | } else {
82 | return await graphClient.api(resource)
83 | .get()
84 | .catch((err) => {
85 | throw err;
86 | });
87 | }
88 | }
89 |
90 | const getResourceTime = function (resource) {
91 |
92 | if("lastModifiedDateTime" in resource) {
93 | return (new Date(resource.lastModifiedDateTime).getTime()) / 1000;
94 | } else {
95 | return (new Date().getTime()) / 1000;
96 | }
97 | }
98 |
99 | exports.deleteSubscription = deleteSubscription;
100 | exports.createSubscription = createSubscription;
101 | exports.updateSubscriptionExpiration = updateSubscriptionExpiration;
102 | exports.listSubscriptions = listSubscriptions;
103 | exports.getResource = getResource;
104 | exports.getResourceTime = getResourceTime;
--------------------------------------------------------------------------------
/graph/helpers/sourcetypes.json:
--------------------------------------------------------------------------------
1 | {
2 | "communications/callRecords": "m365:teams:callRecord"
3 | }
--------------------------------------------------------------------------------
/graph/helpers/splunk.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 Splunk Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | const axios = require('axios');
18 | const sourcetypes = require('../helpers/sourcetypes.json');
19 |
20 | const RESOURCE_REGEX = /^(?[^\/]*\/[^\/]*)/
21 | // RESOURCE_REGEX matches everything up to the second instance of '/'
22 | // Example:
23 | // if the resource is communications/callRecords/123456
24 | // then the extration will be communications/callRecords
25 |
26 | const getSourcetype = function(resource) {
27 |
28 | let resourceType = "unknown";
29 | if(RESOURCE_REGEX.test(resource)) {
30 | resourceType = resource.match(RESOURCE_REGEX).groups['resourceType'];
31 | }
32 |
33 | let sourcetype = sourcetypes[resourceType];
34 |
35 | if(sourcetype) {
36 | return sourcetype;
37 | } else {
38 | return "m365:unknown"
39 | }
40 |
41 | }
42 |
43 | const logInfo = async function(message) {
44 | let payload = {
45 | "event": message,
46 | "sourcetype": "m365:log:info"
47 | }
48 |
49 | sendToHEC(payload)
50 | .catch((err) => {
51 | return err;
52 | });
53 | }
54 |
55 | const logError = async function(message) {
56 | let payload = {
57 | "event": message,
58 | "sourcetype": "m365:log:error"
59 | }
60 |
61 | sendToHEC(payload)
62 | .catch((err) => {
63 | return err;
64 | });
65 | }
66 |
67 | const logWarning = async function(message) {
68 | let payload = {
69 | "event": message,
70 | "sourcetype": "m365:log:warn"
71 | }
72 |
73 | sendToHEC(payload)
74 | .catch((err) => {
75 | return err;
76 | });
77 | }
78 |
79 | const sendToHEC = async function(payload) {
80 |
81 | let headers = {
82 | "Authorization": `Splunk ${process.env["SPLUNK_HEC_TOKEN"]}`
83 | }
84 |
85 | return await axios.post(process.env["SPLUNK_HEC_URL"], payload, {headers: headers})
86 | .catch((err) => {
87 | context.log.error(`Error posting to Splunk HTTP Event Collector: ${err}`);
88 | return err;
89 | });
90 |
91 | }
92 |
93 | exports.getSourcetype = getSourcetype;
94 | exports.sendToHEC = sendToHEC;
95 | exports.logInfo = logInfo;
96 | exports.logError = logError;
97 | exports.logWarning = logWarning;
--------------------------------------------------------------------------------
/graph/host.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0",
3 | "logging": {
4 | "applicationInsights": {
5 | "samplingSettings": {
6 | "isEnabled": true,
7 | "excludedTypes": "Request"
8 | }
9 | }
10 | },
11 | "extensionBundle": {
12 | "id": "Microsoft.Azure.Functions.ExtensionBundle",
13 | "version": "[2.*, 3.0.0)"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/graph/list-subscriptions/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "bindings": [
3 | {
4 | "authLevel": "function",
5 | "type": "httpTrigger",
6 | "direction": "in",
7 | "name": "req",
8 | "methods": [
9 | "get",
10 | "post"
11 | ]
12 | },
13 | {
14 | "type": "http",
15 | "direction": "out",
16 | "name": "res"
17 | }
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/graph/list-subscriptions/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 Splunk Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | const graph = require('../helpers/graph');
18 | const splunk = require('../helpers/splunk');
19 |
20 | module.exports = async function (context, req) {
21 | let msg = '[list-subscriptions] function triggered';
22 | context.log(msg);
23 | splunk.logInfo(msg);
24 |
25 | await graph.listSubscriptions()
26 | .then((subscriptions) => {
27 | context.res = {
28 | body: JSON.stringify(subscriptions, null, 4)
29 | };
30 | })
31 | .catch((err) => {
32 | msg = `[list-subscriptions] error getting subscriptions: ${JSON.stringify(err, null, 4)}`
33 | context.log.err(msg);
34 | splunk.logError(msg);
35 | context.res = {
36 | body: msg
37 | }
38 | });
39 | };
--------------------------------------------------------------------------------
/graph/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "azure-functions-graph-to-splunk",
3 | "version": "1.0.0",
4 | "description": "",
5 | "scripts": {
6 | "start": "func start",
7 | "test": "echo \"No tests yet...\""
8 | },
9 | "dependencies": {
10 | "@azure/storage-blob": "latest",
11 | "@microsoft/microsoft-graph-client": "^2.0.0",
12 | "adal-node": "^0.2.1",
13 | "axios": "^1.6.8",
14 | "isomorphic-fetch": "^2.2.1"
15 | },
16 | "devDependencies": {}
17 | }
18 |
--------------------------------------------------------------------------------
/graph/process-notification-queue/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "bindings": [
3 | {
4 | "name": "myQueueItem",
5 | "type": "queueTrigger",
6 | "direction": "in",
7 | "queueName": "notification-queue",
8 | "connection": "AzureWebJobsStorage"
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/graph/process-notification-queue/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 Splunk Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | const graph = require('../helpers/graph');
18 | const splunk = require('../helpers/splunk');
19 |
20 | module.exports = async function (context, notificationQueueItem) {
21 | let msg = `[process-notification-queue] queue trigger function processed work item', ${JSON.stringify(notificationQueueItem)}`
22 | context.log(msg);
23 | splunk.logInfo(msg);
24 |
25 | let sourcetype = splunk.getSourcetype(notificationQueueItem);
26 | await graph.getResource(notificationQueueItem)
27 | .then((resource) => {
28 | let payload = {
29 | "event": JSON.stringify(resource),
30 | "sourcetype": sourcetype,
31 | "time": graph.getResourceTime(resource)
32 | }
33 | return payload;
34 | })
35 | .then((payload) => {
36 | splunk.sendToHEC(payload)
37 | .catch((err) => {
38 | let msg = `[process-notification-queue] error posting to Splunk HTTP Event Collector: ${err}`;
39 | context.log.error(msg);
40 | return err;
41 | });
42 | })
43 | .catch((err) => {
44 | let msg = `[process-notification-queue] error: ${JSON.stringify(err, null, 4)}`;
45 | context.log.error(msg);
46 | splunk.logError(msg);
47 | throw err;
48 | });
49 | };
--------------------------------------------------------------------------------
/graph/proxies.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/proxies",
3 | "proxies": {}
4 | }
5 |
--------------------------------------------------------------------------------
/graph/subscription-webhook/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "bindings": [
3 | {
4 | "authLevel": "anonymous",
5 | "type": "httpTrigger",
6 | "direction": "in",
7 | "name": "req",
8 | "methods": [
9 | "get",
10 | "post"
11 | ]
12 | },
13 | {
14 | "type": "http",
15 | "direction": "out",
16 | "name": "res"
17 | },
18 | {
19 | "type": "queue",
20 | "direction": "out",
21 | "name": "notificationQueue",
22 | "queueName": "notification-queue",
23 | "connection": "AzureWebJobsStorage"
24 | }
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/graph/subscription-webhook/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 Splunk Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | const utils = require('../helpers/config');
18 | const splunk = require('../helpers/splunk');
19 |
20 | module.exports = async function (context, req) {
21 | let msg = '[subscription-webhook] received HTTP request on the subscription-webhook.';
22 | context.log(msg);
23 | splunk.logInfo(msg);
24 |
25 | // Make sure we have values for the application
26 | if (!utils.checkConfig()) {
27 | context.res = {
28 | status: 500,
29 | body: "Please check Function App parameters. One or more required parameters are missing values."
30 | };
31 | return;
32 | }
33 |
34 | if (req.query.validationToken) {
35 | // The first time this webhook is called from a Graph subscription, a validationToken will be sent to verify this is a legit webhook to receive data.
36 | // We just need to respond back with the validationToken.
37 | let msg = '[subscription-webhook] received request to validate subscription webhook: ' + req.query.validationToken;
38 | context.log(msg);
39 | splunk.logInfo(msg);
40 | context.res = {
41 | body: req.query.validationToken
42 | };
43 | }
44 | else {
45 | // Graph is sending us some data!
46 | msg = '[subscription-webhook] received a subscription notification: ' + JSON.stringify(req.body);
47 | context.log(msg);
48 | splunk.logInfo(msg);
49 | try {
50 | splunk.logInfo(msg);
51 | for (let i = 0; i < req.body.value.length; i++) {
52 | context.bindings.notificationQueue = req.body.value[i].resource;
53 | }
54 | // Send a status of 'Accepted'
55 | context.res.status = 202;
56 | } catch(err) {
57 | context.log(err.message);
58 | context.res.status = 500;
59 | }
60 | }
61 | };
62 |
--------------------------------------------------------------------------------
/graph/update-subscriptions/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "bindings": [
3 | {
4 | "name": "myTimer",
5 | "type": "timerTrigger",
6 | "direction": "in",
7 | "schedule": "0 0 */2 * * *"
8 | }
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/graph/update-subscriptions/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 Splunk Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | const { BlobServiceClient, BlockBlobClient } = require("@azure/storage-blob");
18 | const blobServiceClient = BlobServiceClient.fromConnectionString(process.env["AzureWebJobsStorage"]);
19 | const containerClient = blobServiceClient.getContainerClient('subscriptions');
20 | const graph = require('../helpers/graph');
21 | const splunk = require('../helpers/splunk');
22 |
23 | // A helper method used to read a Node.js readable stream into string
24 | async function streamToString(readableStream) {
25 | return new Promise((resolve, reject) => {
26 | const chunks = [];
27 | readableStream.on("data", (data) => {
28 | chunks.push(data.toString());
29 | });
30 | readableStream.on("end", () => {
31 | resolve(chunks.join(""));
32 | });
33 | readableStream.on("error", reject);
34 | });
35 | }
36 |
37 | module.exports = async function (context, myTimer) {
38 |
39 | // Get the subscriptions from the blob container
40 | let subscriptionsToCheck = [];
41 |
42 | try {
43 | for await (let blob of containerClient.listBlobsFlat()) {
44 | let blockBlobClient = containerClient.getBlockBlobClient(blob.name);
45 | let downloadBlockBlobResponse = await blockBlobClient.download(0);
46 | let subscription = await streamToString(downloadBlockBlobResponse.readableStreamBody);
47 | subscriptionsToCheck.push(JSON.parse(subscription));
48 | // Example subscription {"subscriptionId":"123","subscriptionExpirationDateTime":"YYYY-MM-DDThh:00:00.000Z"}
49 | }
50 | } catch(err) {
51 | context.log.error(err);
52 | splunk.logError(JSON.stringify(err));
53 | context.res = {
54 | body: `Error getting blobs: ${err}`
55 | };
56 | return;
57 | }
58 |
59 | // Loop through the subscriptions and check if they need updating.
60 | for (let i = 0; i < subscriptionsToCheck.length; i++) {
61 | let subscription = subscriptionsToCheck[i];
62 | splunk.logInfo(`[update-subscriptions] checking subscription ${JSON.stringify(subscription)}`);
63 |
64 | if (subscriptionHasExpired(subscription.subscriptionExpirationDateTime)) {
65 | let msg = `[update-subscriptions] a subscription with subscription Id '${subscription.subscriptionId}' has expired. Removing the blob item...`
66 | context.log.warn(msg);
67 | splunk.logWarning(msg);
68 | containerClient.deleteBlob(subscription.subscriptionId);
69 | continue;
70 | }
71 |
72 | if (!subscriptionExpiresSoon(subscription.subscriptionExpirationDateTime)) {
73 | continue;
74 | }
75 |
76 | // Create a Date 2 days in the future
77 | let expirationDateTime = new Date();
78 | expirationDateTime.setDate(expirationDateTime.getDate() + 2);
79 | let newSubscriptionExpirationDateTime = expirationDateTime.toISOString();
80 | splunk.logInfo(`[update-subscriptions] updating subscription ${JSON.stringify(subscription)}. New expiration: ${newSubscriptionExpirationDateTime}`);
81 |
82 | await graph.updateSubscriptionExpiration(subscription.subscriptionId, newSubscriptionExpirationDateTime)
83 | .then(() => {
84 | // Update the blob with the new subscription expiration
85 | subscriptionBlobItem = {
86 | "subscriptionId": subscription.subscriptionId,
87 | "subscriptionExpirationDateTime": newSubscriptionExpirationDateTime
88 | }
89 | let blobContent = JSON.stringify(subscriptionBlobItem);
90 | let blobName = subscription.subscriptionId;
91 | let blockBlobClient = containerClient.getBlockBlobClient(blobName);
92 | blockBlobClient.upload(blobContent, blobContent.length);
93 | })
94 | .catch((err) => {
95 | let errorMsg = `[update-subscriptions] could not update subscription from Graph: ${subscription.subscriptionId}, error: ${JSON.stringify(err)}`
96 | context.log.error(errorMsg);
97 | splunk.logError(errorMsg);
98 | context.res = {
99 | body: errorMsg
100 | };
101 | return;
102 | });
103 |
104 | }
105 | };
106 |
107 | function subscriptionHasExpired(expirationDateTime) {
108 |
109 | let subscriptionExpirationDateTime = Date.parse(expirationDateTime);
110 |
111 | let now = new Date();
112 |
113 | // has it expired
114 | return subscriptionExpirationDateTime < now;
115 | }
116 |
117 | function subscriptionExpiresSoon(expirationDateTime) {
118 |
119 | let subscriptionExpirationDateTime = Date.parse(expirationDateTime);
120 |
121 | // Create a Date 1 day in the future
122 | let now = new Date();
123 | let futureDateTime = now.setDate(now.getDate() + 1);
124 |
125 | // If the subscription's expiration date is less than one day in the future,
126 | // we will consider it close to expiration and renew.
127 | return subscriptionExpirationDateTime < futureDateTime;
128 | }
--------------------------------------------------------------------------------
/graph/~/.azure-functions-core-tools/Functions/ExtensionBundles/Microsoft.Azure.Functions.ExtensionBundle/1.3.0/bundle.json:
--------------------------------------------------------------------------------
1 | {"id":"Microsoft.Azure.Functions.ExtensionBundle","version":"1.3.0"}
--------------------------------------------------------------------------------
/graph/~/.azure-functions-core-tools/Functions/ExtensionBundles/Microsoft.Azure.Functions.ExtensionBundle/1.3.0/extensions.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | v3
4 | netcoreapp3.1
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/storage-hec/.funcignore:
--------------------------------------------------------------------------------
1 | *.js.map
2 | *.ts
3 | .git*
4 | .vscode
5 | local.settings.json
6 | test
7 | tsconfig.json
--------------------------------------------------------------------------------
/storage-hec/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 |
24 | # nyc test coverage
25 | .nyc_output
26 |
27 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
28 | .grunt
29 |
30 | # Bower dependency directory (https://bower.io/)
31 | bower_components
32 |
33 | # node-waf configuration
34 | .lock-wscript
35 |
36 | # Compiled binary addons (https://nodejs.org/api/addons.html)
37 | build/Release
38 |
39 | # Dependency directories
40 | node_modules/
41 | jspm_packages/
42 |
43 | # TypeScript v1 declaration files
44 | typings/
45 |
46 | # Optional npm cache directory
47 | .npm
48 |
49 | # Optional eslint cache
50 | .eslintcache
51 |
52 | # Optional REPL history
53 | .node_repl_history
54 |
55 | # Output of 'npm pack'
56 | *.tgz
57 |
58 | # Yarn Integrity file
59 | .yarn-integrity
60 |
61 | # dotenv environment variables file
62 | .env
63 | .env.test
64 |
65 | # parcel-bundler cache (https://parceljs.org/)
66 | .cache
67 |
68 | # next.js build output
69 | .next
70 |
71 | # nuxt.js build output
72 | .nuxt
73 |
74 | # vuepress build output
75 | .vuepress/dist
76 |
77 | # Serverless directories
78 | .serverless/
79 |
80 | # FuseBox cache
81 | .fusebox/
82 |
83 | # DynamoDB Local files
84 | .dynamodb/
85 |
86 | # TypeScript output
87 | dist
88 | out
89 |
90 | # Azure Functions artifacts
91 | bin
92 | obj
93 | appsettings.json
94 | local.settings.json
--------------------------------------------------------------------------------
/storage-hec/README.md:
--------------------------------------------------------------------------------
1 | # Azure Functions for Sending Azure Storage data to a Splunk HTTP Event Collector
2 | Azure storage operations can trigger serverless Azure Functions. Azure Functions can further process the raw events in near real-time.
3 |
4 |
5 |
6 |
7 |
8 | This repository contains a collection of Azure Functions for:
9 | * Processing [Network Security Group (NSG) Flow Log](https://learn.microsoft.com/azure/network-watcher/network-watcher-nsg-flow-logging-overview) blobs as they are written to an Azure Blob container.
10 | * This function can separate batched events (events in a `records[]` array) into individual events
11 | * Optionally, this function can denormalize the events by making each flow tuple a distinct Splunk event
12 | * Formatting events in the `event` format for a Splunk HTTP Event Collector
13 | * Sending event data to Splunk via [HTTP Event Collector](https://docs.splunk.com/Documentation/Splunk/latest/Data/UsetheHTTPEventCollector)
14 | * Writing event data to a separate Storage Blob container if data cannot successfully be sent to Splunk
15 | * The [Splunk Add-on for Microsoft Cloud Services](https://splunkbase.splunk.com/app/3110/) can be utilized to retrieve Storage Blob data from the separate container
16 |
17 | ## Getting Started
18 |
19 | ### 1. Create an HTTP Event Collector token in your Spunk Environment
20 | An HTTP Event Collector receives data pushed from the Azure Functions. Refer to the Splunk documentation for [setting up an HTTP Event Collector input](https://docs.splunk.com/Documentation/Splunk/latest/Data/UsetheHTTPEventCollector) in your Splunk Enterprise or Splunk Cloud environment.
21 |
22 | ### 2. Create an Azure Storage Account
23 | Refer to the Microsoft documentation for [Azure Storage Account setup instructions](https://learn.microsoft.com/azure/storage/common/storage-account-create).
24 |
25 | ### 3. Configuring NSG Flow Logs in the Azure Portal
26 |
27 | * From the Azure Portal, navigate to a Network Watcher instance and select Flow Logs
28 |
29 | 
30 |
31 | * Select a Network Security Group from the list by clicking it
32 |
33 | 
34 |
35 | * Navigate to the correct storage account and then Containers -> insights-logs-networksecuritygroupflowevent
36 |
37 | 
38 |
39 | ### 4. Deploy the functions to Azure
40 |
41 | Use the "Deploy to Azure" button above to deploy the Azure Functions from this repo to your Azure account. During setup, you will be prompted for the following information:
42 |
43 | * Blob Path - this is the blob container containing the NSG Flow logs
44 | * Blob Connection String - this is the connection string for the Azure Storage Account
45 | * NSG Sourcetype
46 | * Splunk [HTTP Event Collector](https://docs.splunk.com/Documentation/Splunk/latest/Data/UsetheHTTPEventCollector) Endpoint
47 | * Splunk [HTTP Event Collector](https://docs.splunk.com/Documentation/Splunk/latest/Data/UsetheHTTPEventCollector) Token
48 | * Denormalize Events
49 | * If true, each flow tuple will be a separate Splunk event
50 | * If false, each Splunk event will contain multiple flow tuples
51 | * Note: see the [NSG log format](https://learn.microsoft.com/azure/network-watcher/network-watcher-nsg-flow-logging-overview#log-format) for more details.
52 |
53 |
54 | ## Securing Azure Function settings
55 | Microsoft stores the above values as [application settings](https://docs.microsoft.com/en-us/azure/azure-functions/functions-how-to-use-azure-function-app-settings#settings). These settings are stored encrypted, but you may opt to transfer one or more of these settings to a Key Vault. Refer to the following documentation for details on this procedure:
56 |
57 | * [Use Key Vault references for App Service and Azure Functions](https://docs.microsoft.com/en-us/azure/app-service/app-service-key-vault-references)
58 |
59 | ## Support
60 | This software is released as-is. Splunk provides no warranty and no support on this software. If you have any issues with the software, please file an issue on the repository.
61 |
--------------------------------------------------------------------------------
/storage-hec/blob-nsg-hec/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "bindings": [
3 | {
4 | "type": "blobTrigger",
5 | "name": "nsgBlobTrigger",
6 | "direction": "in",
7 | "connection": "BLOB_CONNECTION_STRING",
8 | "dataType": "string",
9 | "path": "%BLOB_PATH%/{name}"
10 | },
11 | {
12 | "type": "blob",
13 | "name": "nsgBlobInput",
14 | "direction": "in",
15 | "connection": "BLOB_CONNECTION_STRING",
16 | "dataType": "string",
17 | "path": "%BLOB_PATH%/{name}"
18 | },
19 | {
20 | "name": "outputBlob",
21 | "type": "blob",
22 | "path": "undeliverable-nsg-events/{rand-guid}-{name}",
23 | "connection": "BLOB_CONNECTION_STRING",
24 | "direction": "out"
25 | }
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/storage-hec/blob-nsg-hec/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2022 Splunk Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | const splunk = require('../helpers/splunk');
17 | module.exports = async function (context) {
18 |
19 | await splunk
20 | .sendToHEC(context.bindings.nsgBlobInput)
21 | .catch(err => {
22 | context.log.error(`Error posting to Splunk HTTP Event Collector: ${err}`);
23 |
24 | // If the event was not successfully sent to Splunk, drop the event in a storage blob container undeliverable-nsg-events
25 | context.bindings.outputBlob = context.bindings.nsgBlobInput;
26 | })
27 | context.done();
28 | };
--------------------------------------------------------------------------------
/storage-hec/deploy/azureDeploy.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "appName": {
6 | "type": "string",
7 | "metadata": {
8 | "description": "The name of the function app that you wish to create."
9 | }
10 | },
11 | "storageAccountType": {
12 | "type": "string",
13 | "defaultValue": "Standard_LRS",
14 | "allowedValues": [
15 | "Standard_LRS",
16 | "Standard_GRS",
17 | "Standard_RAGRS"
18 | ],
19 | "metadata": {
20 | "description": "Storage Account type"
21 | }
22 | },
23 | "storageAccountTLS": {
24 | "type": "string",
25 | "defaultValue": "TLS1_2",
26 | "allowedValues": [
27 | "TLS1_0",
28 | "TLS1_1",
29 | "TLS1_2"
30 | ],
31 | "metadata": {
32 | "description": "Storage Account TLS version"
33 | }
34 | },
35 | "nsgConnectionString": {
36 | "type": "string",
37 | "metadata": {
38 | "description": "Connection string for the storage account storing the NSG flow logs"
39 | }
40 | },
41 | "nsgContainer": {
42 | "type": "string",
43 | "defaultValue": "insights-logs-networksecuritygroupflowevent",
44 | "metadata": {
45 | "description": "Blob container for NSG flow logs"
46 | }
47 | },
48 | "nsgSourceType": {
49 | "type": "string",
50 | "defaultValue": "azure:nsg:flowlog",
51 | "metadata": {
52 | "description": "Splunk source type for the NSG flow logs"
53 | }
54 | },
55 | "denormalizeEvents": {
56 | "type": "bool",
57 | "metadata": {
58 | "description": ""
59 | }
60 | },
61 | "githubRepoURL": {
62 | "type": "string",
63 | "defaultValue": "https://github.com/splunk/azure-functions-splunk.git"
64 | },
65 | "githubRepoBranch": {
66 | "type": "string",
67 | "defaultValue": "master"
68 | },
69 | "githubRepoProject": {
70 | "type": "string",
71 | "defaultValue": "storage-hec"
72 | },
73 | "SplunkEndpoint": {
74 | "type": "string",
75 | "metadata": {
76 | "description": "Splunk HEC endpoint. For details, refer to the Splunk documentation."
77 | }
78 | },
79 | "SplunkToken": {
80 | "type": "string",
81 | "metadata": {
82 | "description": "Splunk HEC token. For details, refer to the Splunk documentation."
83 | }
84 | }
85 | },
86 | "variables": {
87 | "functionAppName": "[parameters('appName')]",
88 | "storageAccountName": "[concat(uniquestring(resourceGroup().id), 'functions')]"
89 | },
90 | "resources": [
91 | {
92 | "type": "Microsoft.Storage/storageAccounts",
93 | "name": "[variables('storageAccountName')]",
94 | "apiVersion": "2019-06-01",
95 | "location": "[resourceGroup().location]",
96 | "kind": "Storage",
97 | "sku": {
98 | "name": "[parameters('storageAccountType')]"
99 | },
100 | "properties": {
101 | "minimumTlsVersion": "[parameters('storageAccountTLS')]"
102 | }
103 | },
104 | {
105 | "apiVersion": "2015-05-01",
106 | "name": "[variables('functionAppName')]",
107 | "type": "Microsoft.Insights/components",
108 | "kind": "web",
109 | "location": "[resourceGroup().location]",
110 | "properties": {
111 | "Application_Type": "web"
112 | }
113 | },
114 | {
115 | "apiVersion": "2019-08-01",
116 | "type": "Microsoft.Web/sites",
117 | "name": "[variables('functionAppName')]",
118 | "location": "[resourceGroup().location]",
119 | "kind": "functionapp",
120 | "dependsOn": [
121 | "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"
122 | ],
123 | "properties": {
124 | "siteConfig": {
125 | "appSettings": [
126 | {
127 | "name": "AzureWebJobsStorage",
128 | "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value)]"
129 | },
130 | {
131 | "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
132 | "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value)]"
133 | },
134 | {
135 | "name": "APPINSIGHTS_INSTRUMENTATIONKEY",
136 | "value": "[reference(concat('microsoft.insights/components/', variables('functionAppName')), '2015-05-01').InstrumentationKey]"
137 | },
138 | {
139 | "name": "FUNCTIONS_WORKER_RUNTIME",
140 | "value": "node"
141 | },
142 | {
143 | "name": "WEBSITE_NODE_DEFAULT_VERSION",
144 | "value": "~12"
145 | },
146 | {
147 | "name": "FUNCTIONS_EXTENSION_VERSION",
148 | "value": "~3"
149 | },
150 | {
151 | "name": "SPLUNK_HEC_TOKEN",
152 | "value": "[parameters('SplunkToken')]"
153 | },
154 | {
155 | "name": "SPLUNK_HEC_URL",
156 | "value": "[parameters('SplunkEndpoint')]"
157 | },
158 | {
159 | "name": "Project",
160 | "value": "[parameters('githubRepoProject')]"
161 | },
162 | {
163 | "name": "BLOB_PATH",
164 | "value": "[parameters('nsgContainer')]"
165 | },
166 | {
167 | "name": "BLOB_CONNECTION_STRING",
168 | "value": "[parameters('nsgConnectionString')]"
169 | },
170 | {
171 | "name": "NSG_SOURCETYPE",
172 | "value": "[parameters('nsgSourceType')]"
173 | },
174 | {
175 | "name": "DENORMALIZE_EVENTS",
176 | "value": "[parameters('denormalizeEvents')]"
177 | }
178 | ]
179 | }
180 | },
181 | "resources": [
182 | {
183 | "apiVersion": "2016-08-01",
184 | "name": "web",
185 | "type": "sourcecontrols",
186 | "dependsOn": [
187 | "[resourceId('Microsoft.Web/Sites', variables('functionAppName'))]"
188 | ],
189 | "properties": {
190 | "RepoUrl": "[parameters('githubRepoURL')]",
191 | "branch": "[parameters('githubRepoBranch')]",
192 | "IsManualIntegration": true
193 | }
194 | }
195 | ]
196 | }
197 | ]
198 | }
199 |
--------------------------------------------------------------------------------
/storage-hec/deploy/azureDeploy.portal.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/0.1.2-preview/CreateUIDefinition.MultiVm.json#",
3 | "handler": "Microsoft.Azure.CreateUIDef",
4 | "version": "0.1.2-preview",
5 | "parameters": {
6 | "basics": [
7 | {
8 | "name": "appServiceAvailabilityApi",
9 | "type": "Microsoft.Solutions.ArmApiControl",
10 | "visible": false,
11 | "request": {
12 | "method": "POST",
13 | "path": "[concat(subscription().id, '/providers/Microsoft.Web/checknameavailability?api-version=2022-03-01')]",
14 | "body": "[parse(concat('{\"name\":\"', concat('', steps('basics').appName), '\", \"type\": \"Microsoft.Web/sites\"}'))]"
15 | }
16 | },
17 | {
18 | "name": "appName",
19 | "label": "Function App Name",
20 | "type": "Microsoft.Common.TextBox",
21 | "toolTip": "The name of the function app that you wish to create.",
22 | "visible": true,
23 | "placeholder": "functionappname",
24 | "constraints": {
25 | "validations": [
26 | {
27 | "isValid": "[not(equals(steps('basics').appServiceAvailabilityApi.nameAvailable, false))]",
28 | "message": "[concat('Error with the url: ', steps('basics').appName, '. Reason: ', steps('basics').appServiceAvailabilityApi.reason)]"
29 | }
30 | ],
31 | "required": true
32 | }
33 | },
34 | {
35 | "name": "storageAccountType",
36 | "label": "Storage Account Type",
37 | "type": "Microsoft.Common.DropDown",
38 | "toolTip": "The storage account will contain a blob container for undeliverable events.",
39 | "constraints": {
40 | "required": true,
41 | "allowedValues": [
42 | {
43 | "label": "Standard Locally Redundant Storage",
44 | "value": "Standard_LRS"
45 | },
46 | {
47 | "label": "Standard Geo Replicated Storage",
48 | "value": "Standard_GRS"
49 | },
50 | {
51 | "label": "Standard Read-Access Geo Replicated Storage",
52 | "value": "Standard_RAGRS"
53 | },
54 | {
55 | "label": "Standard Zone Redundant Storage",
56 | "value": "Standard_ZRS"
57 | }
58 | ]
59 | }
60 | },
61 | {
62 | "name": "storageAccountTLS",
63 | "label": "Storage Account Minimum TLS Version",
64 | "type": "Microsoft.Common.DropDown",
65 | "toolTip": "Minimum TLS Version",
66 | "constraints": {
67 | "required": true,
68 | "allowedValues": [
69 | {
70 | "label": "TLS 1.0",
71 | "value": "TLS1_0"
72 | },
73 | {
74 | "label": "TLS 1.1",
75 | "value": "TLS1_1"
76 | },
77 | {
78 | "label": "TLS 1.2",
79 | "value": "TLS1_2"
80 | }
81 | ]
82 | }
83 | },
84 | {
85 | "name": "nsgSection",
86 | "type": "Microsoft.Common.Section",
87 | "label": "NSG Location Details",
88 | "elements": [
89 | {
90 | "name": "nsgConnectionString",
91 | "label": "Storage Account Connection String",
92 | "type": "Microsoft.Common.TextBox",
93 | "toolTip": "The connection string to the stroage account containing NSG Flow Logs",
94 | "constraints": {
95 | "required": true
96 | }
97 | },
98 | {
99 | "name": "nsgContainer",
100 | "label": "NSG Container",
101 | "type": "Microsoft.Common.TextBox",
102 | "toolTip": "The blob container that contains the NSG Flow Logs",
103 | "defaultValue": "insights-logs-networksecuritygroupflowevent",
104 | "constraints": {
105 | "required": true
106 | }
107 | },
108 | {
109 | "name": "nsgSourcetype",
110 | "label": "NSG Source Type",
111 | "type": "Microsoft.Common.TextBox",
112 | "toolTip": "Splunk Souretype for the NSG Logs",
113 | "defaultValue": "azure:nsg:flowlog",
114 | "constraints": {
115 | "required": true
116 | }
117 | },
118 | {
119 | "name": "denormalizeEvents",
120 | "label": "Denormalize Events",
121 | "type": "Microsoft.Common.DropDown",
122 | "toolTip": "",
123 | "defaultValue": ["True"],
124 | "multiselect": false,
125 | "multiLine": true,
126 | "constraints": {
127 | "allowedValues": [
128 | {
129 | "label": "True",
130 | "description": "Separate flow tuples into individual Splunk events",
131 | "value": "true"
132 | },
133 | {
134 | "label": "False",
135 | "description": "Keep multiple flow tuples in a single Splunk event",
136 | "value": "false"
137 | }
138 | ],
139 | "required": true
140 | }
141 | }
142 | ]
143 | },
144 | {
145 | "name": "splunkHECSection",
146 | "type": "Microsoft.Common.Section",
147 | "label": "Splunk HTTP Event Collector Details",
148 | "elements": [
149 | {
150 | "name": "splunkHECEndpoint",
151 | "label": "Splunk HEC Endpoint",
152 | "toolTip": "URL for the Splunk HTTP Event Collector.",
153 | "type": "Microsoft.Common.TextBox",
154 | "placeholder": "https://:/services/collector/event",
155 | "constraints": {
156 | "required": true
157 | }
158 | },
159 | {
160 | "name": "splunkHECToken",
161 | "label": "Splunk HEC Token",
162 | "toolTip": "Splunk HTTP Event Collector Token.",
163 | "type": "Microsoft.Common.TextBox",
164 | "constraints": {
165 | "required": true
166 | }
167 | }
168 | ]
169 | },
170 | {
171 | "name": "repoSection",
172 | "type": "Microsoft.Common.Section",
173 | "label": "Function Repository",
174 | "elements": [
175 | {
176 | "name": "repoURL",
177 | "label": "Repository URL",
178 | "toolTip": "URL for the function code.",
179 | "type": "Microsoft.Common.TextBox",
180 | "defaultValue": "https://github.com/splunk/azure-functions-splunk.git",
181 | "constraints": {
182 | "required": true
183 | }
184 | },
185 | {
186 | "name": "repoBranch",
187 | "label": "Branch",
188 | "defaultValue": "master",
189 | "type": "Microsoft.Common.TextBox",
190 | "constraints": {
191 | "required": true
192 | }
193 | },
194 | {
195 | "name": "repoProject",
196 | "label": "Project",
197 | "toolTip": "If the source code for the function resides in a subdirectory, specify the subdirectory containing the code.",
198 | "type": "Microsoft.Common.TextBox",
199 | "defaultValue": "storage-hec"
200 | }
201 | ]
202 | }
203 | ],
204 | "steps": [],
205 | "outputs": {
206 | "appName": "[steps('basics').appName]",
207 | "storageAccountType": "[steps('basics').storageAccountType]",
208 | "storageAccountTLS": "[steps('basics').storageAccountTLS]",
209 | "nsgConnectionString": "[steps('basics').nsgSection.nsgConnectionString]",
210 | "nsgContainer": "[steps('basics').nsgSection.nsgContainer]",
211 | "nsgSourceType": "[steps('basics').nsgSection.nsgSourcetype]",
212 | "denormalizeEvents": "[steps('basics').nsgSection.denormalizeEvents]",
213 | "SplunkEndpoint": "[steps('basics').splunkHECSection.splunkHECEndpoint]",
214 | "SplunkToken": "[steps('basics').splunkHECSection.splunkHECToken]",
215 | "githubRepoURL": "[steps('basics').repoSection.repoURL]",
216 | "githubRepoBranch": "[steps('basics').repoSection.repoBranch]",
217 | "githubRepoProject": "[steps('basics').repoSection.repoProject]"
218 | }
219 | }
220 | }
221 |
--------------------------------------------------------------------------------
/storage-hec/docs/images/NSG1.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/splunk/azure-functions-splunk/a96cb5f504192d24d26b2daf003c7d9b2b3b471e/storage-hec/docs/images/NSG1.jpeg
--------------------------------------------------------------------------------
/storage-hec/docs/images/NSG2.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/splunk/azure-functions-splunk/a96cb5f504192d24d26b2daf003c7d9b2b3b471e/storage-hec/docs/images/NSG2.jpeg
--------------------------------------------------------------------------------
/storage-hec/docs/images/NSG3.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/splunk/azure-functions-splunk/a96cb5f504192d24d26b2daf003c7d9b2b3b471e/storage-hec/docs/images/NSG3.jpeg
--------------------------------------------------------------------------------
/storage-hec/helpers/config.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 Splunk Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | const checkConfig = function () {
18 | let splunkHecUrl = process.env["SPLUNK_HEC_URL"];
19 | let splunkHecToken = process.env["SPLUNK_HEC_TOKEN"];
20 |
21 | if (!splunkHecUrl || !splunkHecToken) {
22 | return false;
23 | }
24 |
25 | return true;
26 | }
27 |
28 | exports.checkConfig = checkConfig;
--------------------------------------------------------------------------------
/storage-hec/helpers/splunk.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2022 Splunk Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | const axios = require('axios');
17 |
18 | const getEpochTime = function(timeString) {
19 | try {
20 | let epochTime = new Date(timeString).getTime();
21 | return epochTime;
22 | } catch {
23 | return null;
24 | }
25 | }
26 |
27 | const getTimeStamp = function(message) {
28 | if(message.hasOwnProperty('time')) {
29 | return getEpochTime(message["time"]);
30 | }
31 | return null;
32 | }
33 |
34 | const getHECPayload = async function(blobContent) {
35 |
36 | let denormalize = (process.env["DENORMALIZE_EVENTS"].toLowerCase() === 'true')
37 | let sourcetype = process.env["NSG_SOURCETYPE"]
38 | let payload = ''
39 |
40 | // https://learn.microsoft.com/azure/network-watcher/network-watcher-nsg-flow-logging-overview
41 | let records = blobContent.records
42 |
43 | records.forEach(function(record) {
44 |
45 | if (denormalize) {
46 | // Make each flow tuple its own distinct Splunk event
47 | for (i in record.properties.flows) {
48 | let flow = record.properties.flows[i]
49 |
50 | for (i in flow.flows) {
51 | let ruleFlow = flow.flows[i]
52 |
53 | for (i in ruleFlow.flowTuples) {
54 | let ruleFlowTuple = ruleFlow.flowTuples[i]
55 |
56 | let splunkEvent = {}
57 | splunkEvent["time"] = record.time
58 | splunkEvent["systemId"] = record.systemId
59 | splunkEvent["category"] = record.category
60 | splunkEvent["resourceId"] = record.resourceId
61 | splunkEvent["operationName"] = record.operationName
62 | splunkEvent["version"] = record.properties.Version
63 | splunkEvent["rule"] = flow.rule
64 | splunkEvent["mac"] = ruleFlow.mac
65 | splunkEvent["flowTuple"] = ruleFlowTuple
66 |
67 | let recordEvent = {
68 | "event": JSON.stringify(splunkEvent),
69 | "sourcetype": sourcetype
70 | }
71 | let eventTimeStamp = getTimeStamp(record);
72 | if(eventTimeStamp) { recordEvent["time"] = eventTimeStamp; }
73 | payload += JSON.stringify(recordEvent);
74 | }
75 | }
76 | }
77 | } else {
78 | // Make each record its own distince Splunk event
79 | let recordEvent = {
80 | "event": JSON.stringify(record),
81 | "sourcetype": sourcetype
82 | }
83 | let eventTimeStamp = getTimeStamp(record);
84 | if(eventTimeStamp) { recordEvent["time"] = eventTimeStamp; }
85 | payload += JSON.stringify(recordEvent);
86 | }
87 | })
88 |
89 | return payload
90 | }
91 |
92 | const sendToHEC = async function(blobContent) {
93 |
94 | let headers = {
95 | "Authorization": `Splunk ${process.env["SPLUNK_HEC_TOKEN"]}`
96 | }
97 |
98 | await getHECPayload(blobContent)
99 | .then(payload => {
100 | return axios.post(process.env["SPLUNK_HEC_URL"], payload, {headers: headers});
101 | })
102 | .catch(err => {
103 | throw err;
104 | });
105 | }
106 |
107 | exports.sendToHEC = sendToHEC;
--------------------------------------------------------------------------------
/storage-hec/host.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0",
3 | "logging": {
4 | "applicationInsights": {
5 | "samplingSettings": {
6 | "isEnabled": true,
7 | "excludedTypes": "Request"
8 | }
9 | }
10 | },
11 | "extensionBundle": {
12 | "id": "Microsoft.Azure.Functions.ExtensionBundle",
13 | "version": "[3.3.0, 4.0.0)"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/storage-hec/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "storage-hec",
3 | "version": "1.0.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "asynckit": {
8 | "version": "0.4.0",
9 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
10 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
11 | },
12 | "axios": {
13 | "version": "1.2.1",
14 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.2.1.tgz",
15 | "integrity": "sha512-I88cFiGu9ryt/tfVEi4kX2SITsvDddTajXTOFmt2uK1ZVA8LytjtdeyefdQWEf5PU8w+4SSJDoYnggflB5tW4A==",
16 | "requires": {
17 | "follow-redirects": "^1.15.0",
18 | "form-data": "^4.0.0",
19 | "proxy-from-env": "^1.1.0"
20 | }
21 | },
22 | "combined-stream": {
23 | "version": "1.0.8",
24 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
25 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
26 | "requires": {
27 | "delayed-stream": "~1.0.0"
28 | }
29 | },
30 | "delayed-stream": {
31 | "version": "1.0.0",
32 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
33 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
34 | },
35 | "follow-redirects": {
36 | "version": "1.15.6",
37 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
38 | "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA=="
39 | },
40 | "form-data": {
41 | "version": "4.0.0",
42 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
43 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
44 | "requires": {
45 | "asynckit": "^0.4.0",
46 | "combined-stream": "^1.0.8",
47 | "mime-types": "^2.1.12"
48 | }
49 | },
50 | "mime-db": {
51 | "version": "1.52.0",
52 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
53 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
54 | },
55 | "mime-types": {
56 | "version": "2.1.35",
57 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
58 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
59 | "requires": {
60 | "mime-db": "1.52.0"
61 | }
62 | },
63 | "proxy-from-env": {
64 | "version": "1.1.0",
65 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
66 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/storage-hec/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "storage-hec",
3 | "version": "1.0.0",
4 | "description": "Sends Azure Storage data to Splunk's HTTP Event Collector",
5 | "scripts": {
6 | "start": "func start",
7 | "test": "echo \"No tests yet...\""
8 | },
9 | "dependencies": {
10 | "axios": ">=0.21.2"
11 | },
12 | "devDependencies": {}
13 | }
14 |
--------------------------------------------------------------------------------
/storage-hec/proxies.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/proxies",
3 | "proxies": {}
4 | }
5 |
--------------------------------------------------------------------------------