Created Registry Data Access Protocol (RDAP)

As top level domains (and domains in general) have increased, there is a need to be able to lookup information about domains. This project is designed to solve this need (in an albeit limited use case for now) by retrieving domain(s) from Azure Sentinel / Log Analytics, querying the RDAP network for registration information, and then writing that resolution information back in to Azure Sentinel / Log Analytics.
This commit is contained in:
Matt Egen 2021-05-27 11:40:30 -07:00 коммит произвёл GitHub
Родитель 7e44cc33c2
Коммит d7058a0fb1
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
11 изменённых файлов: 969 добавлений и 0 удалений

Просмотреть файл

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31129.286
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RDAPQuery", "RDAPQuery\RDAPQuery.csproj", "{19CEA8D8-3865-4ADD-BFB3-E0325D0F8EB6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{19CEA8D8-3865-4ADD-BFB3-E0325D0F8EB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{19CEA8D8-3865-4ADD-BFB3-E0325D0F8EB6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{19CEA8D8-3865-4ADD-BFB3-E0325D0F8EB6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{19CEA8D8-3865-4ADD-BFB3-E0325D0F8EB6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {00037468-82AC-4026-A86B-57F111815A59}
EndGlobalSection
EndGlobal

Просмотреть файл

@ -0,0 +1,296 @@
// ***********************************************************************
// Assembly : RDAPQuery
// Author : MattEgen
// Created : 04-16-2021
//
// Last Modified By : MattEgen
// Last Modified On : 04-26-2021
// ***********************************************************************
// <copyright file="LogAnalytics.cs" company="">
// Copyright (c) . All rights reserved.
// </copyright>
// <summary></summary>
// ***********************************************************************
using Newtonsoft.Json;
using RestSharp;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace RDAPQuery
{
/// <summary>
/// Contains the logic to call LogAnalytics for querying and writing data
/// </summary>
class LogAnalytics
{
// Update customerId to your Log Analytics workspace ID
/// <summary>
/// The workspace identifier
/// </summary>
static string workspaceID = QueryEngine.GetEnvironmentVariable("WorkspaceID");
// For sharedKey, use either the primary or the secondary Connected Sources client authentication key
/// <summary>
/// The shared key
/// </summary>
static string sharedKey = QueryEngine.GetEnvironmentVariable("SharedKey");
// LogName is name of the event type that is being submitted to Azure Monitor
/// <summary>
/// The log name
/// </summary>
static string LogName = QueryEngine.GetEnvironmentVariable("LogName");
// You can use an optional field to specify the timestamp from the data. If the time field is not specified, Azure Monitor assumes the time is the message ingestion time
/// <summary>
/// The time stamp field
/// </summary>
static string TimeStampField = "";
/// <summary>
/// Gets the bearer token that we need to query LogAnalytics
/// </summary>
/// <returns>Token</returns>
private static async Task<Token> GetBearerToken()
{
#region Local Variables
string tenant_id = QueryEngine.GetEnvironmentVariable("tenant_id");
string baseAddress = string.Format("https://login.microsoftonline.com/{0}/oauth2/token", tenant_id);
string grant_type = QueryEngine.GetEnvironmentVariable("grant_type");
string client_id = QueryEngine.GetEnvironmentVariable("client_id");
string client_secret = QueryEngine.GetEnvironmentVariable("client_secret");
string resource = QueryEngine.GetEnvironmentVariable("resource");
Token token = null;
HttpClient client = new HttpClient();
#endregion
// Set the base address of the HttpClient object
client.BaseAddress = new Uri(baseAddress);
// Add an Accept header for JSON format.
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var form = new Dictionary<string, string>
{
{"grant_type", grant_type},
{"client_id", client_id},
{"client_secret", client_secret},
{"resource",resource }
};
HttpResponseMessage tokenResponse = await client.PostAsync(baseAddress, new FormUrlEncodedContent(form));
if (tokenResponse.IsSuccessStatusCode)
{
var jsonContent = await tokenResponse.Content.ReadAsStringAsync();
token = JsonConvert.DeserializeObject<Token>(jsonContent);
}
else
{
Console.WriteLine("{0} ({1})", (int)tokenResponse.StatusCode, tokenResponse.ReasonPhrase);
}
// Dispose of the client since all HttpClient calls are complete.
client.Dispose();
return token;
}
/// <summary>
/// Represents an OAUTH2 token object
/// </summary>
internal class Token
{
/// <summary>
/// Gets or sets the access token.
/// </summary>
/// <value>The access token.</value>
[JsonProperty("access_token")]
public string AccessToken { get; set; }
/// <summary>
/// Gets or sets the type of the token.
/// </summary>
/// <value>The type of the token.</value>
[JsonProperty("token_type")]
public string TokenType { get; set; }
/// <summary>
/// Gets or sets the expires in.
/// </summary>
/// <value>The expires in.</value>
[JsonProperty("expires_in")]
public int ExpiresIn { get; set; }
/// <summary>
/// Gets or sets the refresh token.
/// </summary>
/// <value>The refresh token.</value>
[JsonProperty("refresh_token")]
public string RefreshToken { get; set; }
}
/// <summary>
/// Writes the json payload to LogAnalytics
/// </summary>
/// <param name="jsonPayload">The json payload.</param>
public static void WriteData(string jsonPayload)
{
// Create a hash for the API signature
var datestring = DateTime.UtcNow.ToString("r");
var jsonBytes = Encoding.UTF8.GetBytes(jsonPayload);
string stringToHash = "POST\n" + jsonBytes.Length + "\napplication/json\n" + "x-ms-date:" + datestring + "\n/api/logs";
string hashedString = BuildSignature(stringToHash, sharedKey);
string signature = "SharedKey " + workspaceID + ":" + hashedString;
PostData(signature, datestring, jsonPayload);
}
/// <summary>
/// Builds a message signature
/// </summary>
/// <param name="message">message to sign</param>
/// <param name="secret">The secret.</param>
/// <returns>string.</returns>
public static string BuildSignature(string message, string secret)
{
var encoding = new System.Text.ASCIIEncoding();
byte[] keyByte = Convert.FromBase64String(secret);
byte[] messageBytes = encoding.GetBytes(message);
using (var hmacsha256 = new HMACSHA256(keyByte))
{
byte[] hash = hmacsha256.ComputeHash(messageBytes);
return Convert.ToBase64String(hash);
}
}
/// <summary>
/// Queries the data.
/// </summary>
/// <param name="query">The query.</param>
/// <returns>QueryResults.</returns>
public static async Task<QueryResults> QueryData(string query)
{
//Get the authorization bearer token
Task<Token> task = GetBearerToken();
Token token = task.Result;
//Now that we have the token, we can use it to call the LogAnalytics service and run our query
HttpClient client = new HttpClient();
string baseAddress = string.Format("https://api.loganalytics.io/v1/workspaces/{0}/query", workspaceID);
// Set the base address of the HttpClient object
client.BaseAddress = new Uri(baseAddress);
// Add an Accept header for JSON format.
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
//Add the AccessToken to the header
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.AccessToken);
var data = new StringContent(query, Encoding.UTF8, "application/json");
HttpResponseMessage queryResponse = await client.PostAsync(baseAddress,data);
if (queryResponse.IsSuccessStatusCode)
{
var jsonContent = await queryResponse.Content.ReadAsStringAsync();
var jsonObject = JsonConvert.DeserializeObject<QueryResults>(jsonContent);
client.Dispose();
return jsonObject;
}
else
{
Console.WriteLine("{0} ({1})", (int)queryResponse.StatusCode, queryResponse.ReasonPhrase);
}
// Dispose of the client since all HttpClient calls are complete.
client.Dispose();
return null;
}
/// <summary>
/// Posts the data to the LogAnalytics service
/// </summary>
/// <param name="signature">The signature</param>
/// <param name="date">The date</param>
/// <param name="json">JSON body to post</param>
public static void PostData(string signature, string date, string json)
{
try
{
string url = "https://" + workspaceID + ".ods.opinsights.azure.com/api/logs?api-version=2016-04-01";
System.Net.Http.HttpClient client = new System.Net.Http.HttpClient();
client.DefaultRequestHeaders.Add("Accept", "application/json");
client.DefaultRequestHeaders.Add("Log-Type", LogName);
client.DefaultRequestHeaders.Add("Authorization", signature);
client.DefaultRequestHeaders.Add("x-ms-date", date);
client.DefaultRequestHeaders.Add("time-generated-field", TimeStampField);
System.Net.Http.HttpContent httpContent = new StringContent(json, Encoding.UTF8);
httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
Task<System.Net.Http.HttpResponseMessage> response = client.PostAsync(new Uri(url), httpContent);
System.Net.Http.HttpContent responseContent = response.Result.Content;
string result = responseContent.ReadAsStringAsync().Result;
Console.WriteLine("Return Result: " + result);
}
catch (Exception excep)
{
Console.WriteLine("API Post Exception: " + excep.Message);
}
}
}
// Root myDeserializedClass = JsonConvert.DeserializeObject<Root>(myJsonResponse);
/// <summary>
/// Class Column.
/// </summary>
public class Column
{
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>The name.</value>
public string name { get; set; }
/// <summary>
/// Gets or sets the type.
/// </summary>
/// <value>The type.</value>
public string type { get; set; }
}
/// <summary>
/// Class Table
/// </summary>
/// <remarks>Defines the table object returned from a call to LogAnalytics</remarks>
public class Table
{
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>The name.</value>
public string name { get; set; }
/// <summary>
/// Gets or sets the columns.
/// </summary>
/// <value>The columns.</value>
public List<Column> columns { get; set; }
/// <summary>
/// Gets or sets the rows.
/// </summary>
/// <value>The rows.</value>
public List<List<string>> rows { get; set; }
}
/// <summary>
/// Class QueryResults.
/// </summary>
public class QueryResults
{
/// <summary>
/// Gets or sets the tables.
/// </summary>
/// <value>The tables.</value>
public List<Table> tables { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,70 @@
{
"$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"resourceGroupName": {
"type": "string",
"defaultValue": "Chromeweb-AIP-RG",
"metadata": {
"_parameterType": "resourceGroup",
"description": "Name of the resource group for the resource. It is recommended to put resources under same resource group for better tracking."
}
},
"resourceGroupLocation": {
"type": "string",
"defaultValue": "eastus",
"metadata": {
"_parameterType": "location",
"description": "Location of the resource group. Resource groups could have different location than resources."
}
},
"resourceLocation": {
"type": "string",
"defaultValue": "[parameters('resourceGroupLocation')]",
"metadata": {
"_parameterType": "location",
"description": "Location of the resource. By default use resource group's location, unless the resource provider is not supported there."
}
}
},
"resources": [
{
"type": "Microsoft.Resources/resourceGroups",
"name": "[parameters('resourceGroupName')]",
"location": "[parameters('resourceGroupLocation')]",
"apiVersion": "2019-10-01"
},
{
"type": "Microsoft.Resources/deployments",
"name": "[concat(parameters('resourceGroupName'), 'Deployment', uniqueString(concat('chromewebaiprgdiag', subscription().subscriptionId)))]",
"resourceGroup": "[parameters('resourceGroupName')]",
"apiVersion": "2019-10-01",
"dependsOn": [
"[parameters('resourceGroupName')]"
],
"properties": {
"mode": "Incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"resources": [
{
"sku": {
"name": "Standard_LRS",
"tier": "Standard"
},
"kind": "Storage",
"name": "chromewebaiprgdiag",
"type": "Microsoft.Storage/storageAccounts",
"location": "[parameters('resourceLocation')]",
"apiVersion": "2017-10-01"
}
]
}
}
}
],
"metadata": {
"_dependencyType": "storage.azure"
}
}

Просмотреть файл

@ -0,0 +1,9 @@
{
"dependencies": {
"storage1": {
"resourceId": "/subscriptions/[parameters('subscriptionId')]/resourceGroups/[parameters('resourceGroupName')]/providers/Microsoft.Storage/storageAccounts/chromewebaiprgdiag",
"type": "storage.azure",
"connectionId": "AzureWebJobsStorage"
}
}
}

Просмотреть файл

@ -0,0 +1,11 @@
{
"dependencies": {
"appInsights1": {
"type": "appInsights"
},
"storage1": {
"type": "storage",
"connectionId": "AzureWebJobsStorage"
}
}
}

Просмотреть файл

@ -0,0 +1,11 @@
{
"dependencies": {
"appInsights1": {
"type": "appInsights.sdk"
},
"storage1": {
"type": "storage.emulator",
"connectionId": "AzureWebJobsStorage"
}
}
}

Просмотреть файл

@ -0,0 +1,486 @@
// ***********************************************************************
// Assembly : RDAPQuery
// Author : MattEgen
// Created : 04-13-2021
//
// Last Modified By : MattEgen
// Last Modified On : 04-27-2021
// ***********************************************************************
// <copyright file="QueryEngine.cs" company="">
// Copyright (c) . All rights reserved.
// </copyright>
// <summary></summary>
// ***********************************************************************
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
namespace RDAPQuery
{
/// <summary>
/// Class QueryEngine.
/// </summary>
public static class QueryEngine
{
/// <summary>
/// The bootstrap URL at IANA. Hardcoded as this value shall never change.
/// </summary>
private const string bootstrapURL = "https://data.iana.org/rdap/dns.json";
/// <summary>
/// URL Paramaters in case we need them TODO: Eliminate this if unneeded.
/// </summary>
private const string urlParameters = "";
/// <summary>
/// Runs on the specified timer to launch the query process
/// </summary>
/// <param name="myTimer">My timer.</param>
/// <param name="log">The log.</param>
[FunctionName("CheckDomains")]
public static void Run([TimerTrigger("0 */5 * * * *")]TimerInfo myTimer, ILogger log)
{
//Log the initiation of the function
log.LogInformation($"CheckDomains Timer trigger function executed at: {DateTime.Now}");
//Query LogAnalytics by calling the "GetDomains" function in the LA workspace
try
{
string queryBody = GetEnvironmentVariable("query_string");
Task<QueryResults> task = LogAnalytics.QueryData(queryBody);
QueryResults results = task.Result;
//Ok, now that we have our domains, for each domain returned, call the bootstrap service and get the responsible server
log.LogInformation(string.Format("Retrieved: {0} rows. Beginning resolution with Bootstrap lookups", results.tables[0].rows.Count));
foreach (List<string> rowData in results.tables[0].rows)
{
RDAPResponseRoot responseRoot = null;
rowData.ForEach(delegate (string value)
{
string TLD = value.Split(".")[1];
string uri = BootStrapTLD(TLD, log);
Console.WriteLine(string.Format("Results of BootStrap call: {0} serviced by {1}", value, uri));
if (uri != string.Empty)
{
//Call the responsible RDAP server
responseRoot = QueryRDAP(string.Format("{0}domain/{1}", uri, value), log);
}
else
{
Console.WriteLine(string.Format("Unable to process URI :'{0}' for value {1}", uri, value));
}
if (responseRoot != null)
{
// Store the results in LogAnalytics.
// Build the JSON body from the results
RDAPUpdate rdapUpdate = new RDAPUpdate();
// there are at least three "events" in the RDAP server response. Only one of them is "interesting" to use here: registration.
foreach (Event rdapEvent in responseRoot.events)
{
if (rdapEvent.eventAction == "registration")
{
// update the update object with our update
rdapUpdate.domainName = value;
rdapUpdate.registrationDate = rdapEvent.eventDate;
// Call the WriteData function to store the data in our LA workspace.
LogAnalytics.WriteData(JsonConvert.SerializeObject(rdapUpdate));
}
}
}
});
}
}
catch (Exception ex)
{
Console.WriteLine(string.Format("Exception: {0}", ex.Message));
}
//Log the completion of the function
log.LogInformation($"CheckDomains Timer trigger function completed at: {DateTime.Now}");
}
/// <summary>
/// Calls the bootstrap server endpoint to find the discrete target for the requested TLD
/// </summary>
/// <param name="requestedTLD">The requested TLD.</param>
/// <param name="log">The log.</param>
/// <returns>System.String.</returns>
public static string BootStrapTLD(string requestedTLD, ILogger log)
{
string queryTLD = requestedTLD;
string responseMessage = string.Empty;
//Log the request
log.LogInformation(string.Format("BootStrapDNS function processed a request for TLD '{0}'", queryTLD));
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(bootstrapURL);
Root rootNode = null;
// Add an Accept header for JSON format.
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
// Get the response from IANA
HttpResponseMessage response = client.GetAsync(urlParameters).Result; // Blocking call!
if (response.IsSuccessStatusCode)
{
var jsonString = response.Content.ReadAsStringAsync();
jsonString.Wait();
rootNode = JsonConvert.DeserializeObject<Root>(jsonString.Result);
foreach (var Service in rootNode.Services)
{
// Each "Service" has two nodes with multiple elements under each
// The first node is the TLDs
// The second node is the RDAP server responsible for servicing the TLDs
// TODO: Really need to clean this up.
foreach (string TLD in Service[0])
{
if (TLD == queryTLD)
{ // return the full server URL( server URI plus the TLD and some formatting)
responseMessage = string.Format("{0}",Service[1][0]);
break; //break out of the foreach()
}
}
}
}
else
{
Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase);
}
// Dispose of the client since all HttpClient calls are complete.
client.Dispose();
// return the URI
return responseMessage;
}
/// <summary>
/// Queries the rdap.
/// </summary>
/// <param name="uri">The URI.</param>
/// <param name="log">The log.</param>
/// <returns>RDAPResponseRoot.</returns>
public static RDAPResponseRoot QueryRDAP(string uri, ILogger log)
{
string responseMessage = string.Empty;
//Log the request
log.LogInformation(string.Format("QueryRDAP function processed a request for URI '{0}'", uri));
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(uri);
RDAPResponseRoot rootNode = null;
// Add an Accept header for JSON format.
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
// Get the response from IANA
HttpResponseMessage response = client.GetAsync(urlParameters).Result; // Blocking call!
if (response.IsSuccessStatusCode)
{
var jsonString = response.Content.ReadAsStringAsync();
jsonString.Wait();
rootNode = JsonConvert.DeserializeObject<RDAPResponseRoot>(jsonString.Result);
}
else
{
Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase);
}
// Dispose of the client since all HttpClient calls are complete.
client.Dispose();
// return the URI
return rootNode;
}
/// <summary>
/// Gets the environment variable from the Azure Function hosting service.
/// </summary>
/// <param name="name">The name.</param>
/// <returns>System.String.</returns>
public static string GetEnvironmentVariable(string name)
{
return System.Environment.GetEnvironmentVariable(name, EnvironmentVariableTarget.Process);
}
}
// Root myDeserializedClass = JsonSerializer.Deserialize<Root>(myJsonResponse);
/// <summary>
/// Deserialization target for json response from calling RDAP bootstrap server
/// </summary>
public class Root
{
/// <summary>
/// Gets or sets the description.
/// </summary>
/// <value>The description.</value>
[JsonPropertyName("description")]
public string Description { get; set; }
/// <summary>
/// Gets or sets the publication.
/// </summary>
/// <value>The publication.</value>
[JsonPropertyName("publication")]
public DateTime Publication { get; set; }
/// <summary>
/// Gets or sets the services.
/// </summary>
/// <value>The services.</value>
[JsonPropertyName("services")]
public List<List<List<string>>> Services { get; set; }
/// <summary>
/// Gets or sets the version.
/// </summary>
/// <value>The version.</value>
[JsonPropertyName("version")]
public string Version { get; set; }
}
// Root myDeserializedClass = JsonConvert.DeserializeObject<Root>(myJsonResponse);
/// <summary>
/// Class Link.
/// </summary>
public class Link
{
/// <summary>
/// Gets or sets the value.
/// </summary>
/// <value>The value.</value>
public string value { get; set; }
/// <summary>
/// Gets or sets the relative.
/// </summary>
/// <value>The relative.</value>
public string rel { get; set; }
/// <summary>
/// Gets or sets the href.
/// </summary>
/// <value>The href.</value>
public string href { get; set; }
/// <summary>
/// Gets or sets the type.
/// </summary>
/// <value>The type.</value>
public string type { get; set; }
}
/// <summary>
/// Class PublicId.
/// </summary>
public class PublicId
{
/// <summary>
/// Gets or sets the type.
/// </summary>
/// <value>The type.</value>
public string type { get; set; }
/// <summary>
/// Gets or sets the identifier.
/// </summary>
/// <value>The identifier.</value>
public string identifier { get; set; }
}
/// <summary>
/// Class Entity.
/// </summary>
public class Entity
{
/// <summary>
/// Gets or sets the name of the object class.
/// </summary>
/// <value>The name of the object class.</value>
public string objectClassName { get; set; }
/// <summary>
/// Gets or sets the roles.
/// </summary>
/// <value>The roles.</value>
public List<string> roles { get; set; }
/// <summary>
/// Gets or sets the vcard array.
/// </summary>
/// <value>The vcard array.</value>
public List<object> vcardArray { get; set; }
/// <summary>
/// Gets or sets the handle.
/// </summary>
/// <value>The handle.</value>
public string handle { get; set; }
/// <summary>
/// Gets or sets the public ids.
/// </summary>
/// <value>The public ids.</value>
public List<PublicId> publicIds { get; set; }
/// <summary>
/// Gets or sets the entities.
/// </summary>
/// <value>The entities.</value>
public List<Entity> entities { get; set; }
}
/// <summary>
/// Class Event.
/// </summary>
public class Event
{
/// <summary>
/// Gets or sets the event action.
/// </summary>
/// <value>The event action.</value>
public string eventAction { get; set; }
/// <summary>
/// Gets or sets the event date.
/// </summary>
/// <value>The event date.</value>
public DateTime eventDate { get; set; }
}
/// <summary>
/// Class SecureDNS.
/// </summary>
public class SecureDNS
{
/// <summary>
/// Gets or sets a value indicating whether [delegation signed].
/// </summary>
/// <value><c>true</c> if [delegation signed]; otherwise, <c>false</c>.</value>
public bool delegationSigned { get; set; }
}
/// <summary>
/// Class Nameserver.
/// </summary>
public class Nameserver
{
/// <summary>
/// Gets or sets the name of the object class.
/// </summary>
/// <value>The name of the object class.</value>
public string objectClassName { get; set; }
/// <summary>
/// Gets or sets the name of the LDH.
/// </summary>
/// <value>The name of the LDH.</value>
public string ldhName { get; set; }
}
/// <summary>
/// Class Notice.
/// </summary>
public class Notice
{
/// <summary>
/// Gets or sets the title.
/// </summary>
/// <value>The title.</value>
public string title { get; set; }
/// <summary>
/// Gets or sets the description.
/// </summary>
/// <value>The description.</value>
public List<string> description { get; set; }
/// <summary>
/// Gets or sets the links.
/// </summary>
/// <value>The links.</value>
public List<Link> links { get; set; }
}
/// <summary>
/// Class RDAPResponseRoot.
/// </summary>
/// Encompases the response from the RDAP query server
public class RDAPResponseRoot
{
/// <summary>
/// Gets or sets the name of the object class.
/// </summary>
/// <value>The name of the object class.</value>
public string objectClassName { get; set; }
/// <summary>
/// Gets or sets the handle.
/// </summary>
/// <value>The handle.</value>
public string handle { get; set; }
/// <summary>
/// Gets or sets the name of the LDH.
/// </summary>
/// <value>The name of the LDH.</value>
public string ldhName { get; set; }
/// <summary>
/// Gets or sets the links.
/// </summary>
/// <value>The links.</value>
public List<Link> links { get; set; }
/// <summary>
/// Gets or sets the status.
/// </summary>
/// <value>The status.</value>
public List<string> status { get; set; }
/// <summary>
/// Gets or sets the entities.
/// </summary>
/// <value>The entities.</value>
public List<Entity> entities { get; set; }
/// <summary>
/// Gets or sets the events.
/// </summary>
/// <value>The events.</value>
public List<Event> events { get; set; }
/// <summary>
/// Gets or sets the secure DNS.
/// </summary>
/// <value>The secure DNS.</value>
public SecureDNS secureDNS { get; set; }
/// <summary>
/// Gets or sets the nameservers.
/// </summary>
/// <value>The nameservers.</value>
public List<Nameserver> nameservers { get; set; }
/// <summary>
/// Gets or sets the rdap conformance.
/// </summary>
/// <value>The rdap conformance.</value>
public List<string> rdapConformance { get; set; }
/// <summary>
/// Gets or sets the notices.
/// </summary>
/// <value>The notices.</value>
public List<Notice> notices { get; set; }
}
/// <summary>
/// Class RDAPUpdate.
/// </summary>
public class RDAPUpdate
{
/// <summary>
/// Gets or sets the name of the domain.
/// </summary>
/// <value>The name of the domain.</value>
public string domainName { get; set; }
/// <summary>
/// Gets or sets the registration date.
/// </summary>
/// <value>The registration date.</value>
public DateTime registrationDate { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AzureFunctionsVersion>v3</AzureFunctionsVersion>
<Authors>Matt Egen</Authors>
<Company />
<Product />
<Description>RDAP Query is an Azure Function based query engine to retrieve Whois / Registry Data Access Protocol data about domains.</Description>
<PackageReleaseNotes>The software is provided "as is," with all faults, defects, bugs, and errors</PackageReleaseNotes>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="3.0.11" />
<PackageReference Include="RestSharp" Version="106.11.7" />
</ItemGroup>
<ItemGroup>
<None Update="host.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="local.settings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</None>
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,11 @@
{
"version": "2.0",
"logging": {
"applicationInsights": {
"samplingSettings": {
"isEnabled": true,
"excludedTypes": "Request"
}
}
}
}

Просмотреть файл

@ -0,0 +1,3 @@
# Whois/Registration Data Access Protocol (RDAP) Query Azure Function
----
As top level domains (and domains in general) have increased, there is a need to be able to lookup information about domains. This project is designed to solve this need (in an albeit limited use case for now) by retrieving domain(s) from Azure Sentinel / Log Analytics, querying the RDAP network for registration information, and then writing that resolution information back in to Azure Sentinel / Log Analytics.

Просмотреть файл

@ -0,0 +1,23 @@
// A dynamic list of domains and TLDs to not bother searching for
let ExcludedDomains = dynamic(["cn","io", "ms", "microsoft.com","google.com"]);
DeviceNetworkEvents
| where TimeGenerated >= ago(1h)
| where isnotempty(RemoteUrl)
// A little cleanup just in case
| extend parsedDomain = case(RemoteUrl contains "//", parse_url(RemoteUrl).Host, RemoteUrl)
| extend cleanDomain = split(parsedDomain,"/")[0]
// Split the resultant domain on the "." character. This will give us all of the domain parts, but we're really only interested in the TLD and root domain
| extend splitDomain = split(cleanDomain,".")
// Use just the last two parts of the domain (e.g. "crazyfunnyhats" and "com") as our domain to lookup / return
| extend Domain = tolower(strcat(splitDomain[array_length(splitDomain)-2],".",splitDomain[array_length(splitDomain)-1]))
// Extract just the TLD so we can use it in the exclusions
| extend TLD = splitDomain[array_length(splitDomain)-1]
// Check to see if we're to ignore this domain / TLD
| where TLD !in(ExcludedDomains)
| where Domain !in(ExcludedDomains)
// summarize the results to remove duplicates
| summarize DistinctDomain = dcount(Domain) by Domain
| project Domain
// Join to the already resolved domains with a leftanti (e.g. if already in ResolvedDomains_CL, then ignore)
| join kind=leftanti (ResolvedDomains_CL
| where TimeGenerated >= ago(90d)) on $left.Domain == $right.domainName_s //Uncomment this line after the FIRST run of the Azure Function.