mirror of
https://github.com/bitwarden/server.git
synced 2025-02-22 02:51:33 +01:00
azure functions project
This commit is contained in:
parent
7cdba3e81a
commit
8218ab468d
@ -41,7 +41,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jobs", "src\Jobs\Jobs.cspro
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper", "..\Dapper\Dapper\Dapper.csproj", "{6951E73D-1761-41F6-B5D3-BEF4C2F73EA3}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BillingUpdater", "util\BillingUpdater\BillingUpdater.csproj", "{A0FBA4DF-2F24-45A6-B188-EBDBD2FAF445}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BillingUpdater", "util\BillingUpdater\BillingUpdater.csproj", "{A0FBA4DF-2F24-45A6-B188-EBDBD2FAF445}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Function", "util\Function\Function.csproj", "{A6C44A84-8E51-4C64-B9C4-7B7C23253345}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@ -95,6 +97,10 @@ Global
|
||||
{A0FBA4DF-2F24-45A6-B188-EBDBD2FAF445}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A0FBA4DF-2F24-45A6-B188-EBDBD2FAF445}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A0FBA4DF-2F24-45A6-B188-EBDBD2FAF445}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A6C44A84-8E51-4C64-B9C4-7B7C23253345}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A6C44A84-8E51-4C64-B9C4-7B7C23253345}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A6C44A84-8E51-4C64-B9C4-7B7C23253345}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A6C44A84-8E51-4C64-B9C4-7B7C23253345}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@ -111,6 +117,7 @@ Global
|
||||
{7DCEBD8F-E5F3-4A3C-BD35-B64341590B74} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84D}
|
||||
{6951E73D-1761-41F6-B5D3-BEF4C2F73EA3} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84D}
|
||||
{A0FBA4DF-2F24-45A6-B188-EBDBD2FAF445} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84E}
|
||||
{A6C44A84-8E51-4C64-B9C4-7B7C23253345} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84E}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {E01CBF68-2E20-425F-9EDB-E0A6510CA92F}
|
||||
|
70
util/Function/BlockIp.cs
Normal file
70
util/Function/BlockIp.cs
Normal file
@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Configuration;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Function.Models;
|
||||
using Microsoft.Azure.WebJobs;
|
||||
using Microsoft.Azure.WebJobs.Host;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Bit.Function
|
||||
{
|
||||
public static class BlockIp
|
||||
{
|
||||
[FunctionName("BlockIp")]
|
||||
public static void Run(
|
||||
[QueueTrigger("blockip", Connection = "")]string myQueueItem,
|
||||
out string outputQueueItem,
|
||||
TraceWriter log)
|
||||
{
|
||||
outputQueueItem = BlockIpAsync(myQueueItem).GetAwaiter().GetResult();
|
||||
log.Info($"C# Queue trigger function processed: {myQueueItem}, outputted: {outputQueueItem}");
|
||||
}
|
||||
|
||||
private static async Task<string> BlockIpAsync(string ipAddress)
|
||||
{
|
||||
var ipWhitelist = ConfigurationManager.AppSettings["WhitelistedIps"];
|
||||
if(ipWhitelist != null && ipWhitelist.Split(',').Contains(ipAddress))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var xAuthEmail = ConfigurationManager.AppSettings["X-Auth-Email"];
|
||||
var xAuthKey = ConfigurationManager.AppSettings["X-Auth-Key"];
|
||||
var zoneId = ConfigurationManager.AppSettings["ZoneId"];
|
||||
|
||||
using(var client = new HttpClient())
|
||||
{
|
||||
client.BaseAddress = new Uri("https://api.cloudflare.com");
|
||||
client.DefaultRequestHeaders.Accept.Clear();
|
||||
client.DefaultRequestHeaders.Add("X-Auth-Email", xAuthEmail);
|
||||
client.DefaultRequestHeaders.Add("X-Auth-Key", xAuthKey);
|
||||
|
||||
var response = await client.PostAsJsonAsync(
|
||||
$"/client/v4/zones/{zoneId}/firewall/access_rules/rules",
|
||||
new
|
||||
{
|
||||
mode = "block",
|
||||
configuration = new
|
||||
{
|
||||
target = "ip",
|
||||
value = ipAddress
|
||||
},
|
||||
notes = $"Rate limit abuse on {DateTime.UtcNow.ToString()}."
|
||||
});
|
||||
|
||||
var responseString = await response.Content.ReadAsStringAsync();
|
||||
var responseJson = JsonConvert.DeserializeObject<AccessRuleResponse>(responseString);
|
||||
|
||||
if(!responseJson.Success)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Uncomment whenever we can delay the returned message. Functions do not support that at this time.
|
||||
return null; //responseJson.Result?.Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
23
util/Function/Function.csproj
Normal file
23
util/Function/Function.csproj
Normal file
@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net461</TargetFramework>
|
||||
<RootNamespace>Bit.Function</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="1.0.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Configuration" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="host.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="local.settings.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
17
util/Function/KeepAlive.cs
Normal file
17
util/Function/KeepAlive.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using Microsoft.Azure.WebJobs;
|
||||
using Microsoft.Azure.WebJobs.Host;
|
||||
|
||||
namespace Bit.Function
|
||||
{
|
||||
public static class KeepAlive
|
||||
{
|
||||
[FunctionName("KeepAlive")]
|
||||
public static void Run(
|
||||
[TimerTrigger("0 */15 * * * *")]TimerInfo myTimer,
|
||||
TraceWriter log)
|
||||
{
|
||||
log.Info($"C# Timer trigger function executed at: {DateTime.Now}");
|
||||
}
|
||||
}
|
||||
}
|
8
util/Function/Models/AccessRuleResponse.cs
Normal file
8
util/Function/Models/AccessRuleResponse.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Bit.Function.Models
|
||||
{
|
||||
public class AccessRuleResponse
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public AccessRuleResultResponse Result { get; set; }
|
||||
}
|
||||
}
|
15
util/Function/Models/AccessRuleResultResponse.cs
Normal file
15
util/Function/Models/AccessRuleResultResponse.cs
Normal file
@ -0,0 +1,15 @@
|
||||
namespace Bit.Function.Models
|
||||
{
|
||||
public class AccessRuleResultResponse
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public ConfigurationResponse Configuration { get; set; }
|
||||
|
||||
public class ConfigurationResponse
|
||||
{
|
||||
public string Target { get; set; }
|
||||
public string Value { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
10
util/Function/Models/ListResult.cs
Normal file
10
util/Function/Models/ListResult.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Bit.Function.Models
|
||||
{
|
||||
public class ListResult
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public List<AccessRuleResultResponse> Result { get; set; }
|
||||
}
|
||||
}
|
132
util/Function/NewHelpdeskTicket.cs
Normal file
132
util/Function/NewHelpdeskTicket.cs
Normal file
@ -0,0 +1,132 @@
|
||||
using System;
|
||||
using System.Configuration;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Mail;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.WebJobs;
|
||||
using Microsoft.Azure.WebJobs.Extensions.Http;
|
||||
using Microsoft.Azure.WebJobs.Host;
|
||||
|
||||
namespace Bit.Function
|
||||
{
|
||||
public static class NewHelpdeskTicket
|
||||
{
|
||||
[FunctionName("NewHelpdeskTicket")]
|
||||
public static HttpResponseMessage Run(
|
||||
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "api/newhelpdeskticket")]HttpRequestMessage req,
|
||||
TraceWriter log)
|
||||
{
|
||||
//ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls |
|
||||
// SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
|
||||
|
||||
var data = req.Content.ReadAsFormDataAsync().Result;
|
||||
if(data == null)
|
||||
{
|
||||
return req.CreateResponse(HttpStatusCode.BadRequest, "No data provided.");
|
||||
}
|
||||
|
||||
if(string.IsNullOrWhiteSpace(data["name"]))
|
||||
{
|
||||
return req.CreateResponse(HttpStatusCode.BadRequest, "Name is required.");
|
||||
}
|
||||
|
||||
if(data["name"].Length > 50)
|
||||
{
|
||||
return req.CreateResponse(HttpStatusCode.BadRequest, "Name must be less than 50 characters.");
|
||||
}
|
||||
|
||||
if(string.IsNullOrWhiteSpace(data["email"]))
|
||||
{
|
||||
return req.CreateResponse(HttpStatusCode.BadRequest, "Email is required.");
|
||||
}
|
||||
|
||||
if(data["email"].Length > 50)
|
||||
{
|
||||
return req.CreateResponse(HttpStatusCode.BadRequest, "Email must be less than 50 characters.");
|
||||
}
|
||||
|
||||
if(!data["email"].Contains("@") || !data["email"].Contains("."))
|
||||
{
|
||||
return req.CreateResponse(HttpStatusCode.BadRequest, "Email is not valid.");
|
||||
}
|
||||
|
||||
if(string.IsNullOrWhiteSpace(data["message"]))
|
||||
{
|
||||
return req.CreateResponse(HttpStatusCode.BadRequest, "Message is required.");
|
||||
}
|
||||
|
||||
//if(!await SubmitApiAsync(data["name"], data["email"], data["message"], log))
|
||||
//{
|
||||
// return req.CreateResponse(HttpStatusCode.BadRequest, "Ticket failed to create.");
|
||||
//}
|
||||
|
||||
SubmitEmail(data["name"], data["email"], data["message"], log);
|
||||
|
||||
return req.CreateResponse(HttpStatusCode.OK, "Ticket created.");
|
||||
}
|
||||
|
||||
private async static Task<bool> SubmitApiAsync(string name, string email, string message, TraceWriter log)
|
||||
{
|
||||
using(var client = new HttpClient())
|
||||
{
|
||||
client.BaseAddress = new Uri("https://bitwarden.freshdesk.com/api/v2");
|
||||
client.DefaultRequestHeaders.Accept.Clear();
|
||||
client.DefaultRequestHeaders.Add("Authorization", MakeFreshdeskApiAuthHeader(log));
|
||||
|
||||
var response = await client.PostAsJsonAsync("tickets",
|
||||
new
|
||||
{
|
||||
name = name,
|
||||
email = email,
|
||||
status = 2,
|
||||
priority = 2,
|
||||
source = 1,
|
||||
subject = "bitwarden.com Website Contact",
|
||||
description = FormatMessage(message)
|
||||
});
|
||||
|
||||
return response.IsSuccessStatusCode;
|
||||
}
|
||||
}
|
||||
|
||||
private static void SubmitEmail(string name, string email, string message, TraceWriter log)
|
||||
{
|
||||
var sendgridApiKey = ConfigurationManager.AppSettings["SendgridApiKey"];
|
||||
var client = new SmtpClient("smtp.sendgrid.net", /*465*/ 587)
|
||||
{
|
||||
//EnableSsl = true,
|
||||
Credentials = new NetworkCredential("apikey", sendgridApiKey)
|
||||
};
|
||||
|
||||
var fromAddress = new MailAddress(email, name, Encoding.UTF8);
|
||||
var mailMessage = new MailMessage(fromAddress, new MailAddress("bitwardencomsupport@bitwarden.freshdesk.com"))
|
||||
{
|
||||
Subject = "bitwarden.com Website Contact",
|
||||
Body = FormatMessage(message),
|
||||
IsBodyHtml = true
|
||||
};
|
||||
|
||||
client.SendCompleted += (s, e) =>
|
||||
{
|
||||
client.Dispose();
|
||||
mailMessage.Dispose();
|
||||
};
|
||||
client.SendAsync(mailMessage, null);
|
||||
}
|
||||
|
||||
private static string FormatMessage(string message)
|
||||
{
|
||||
return message.Replace("\r\n", "\n").Replace("\r", "\n").Replace("\n", "<br>");
|
||||
}
|
||||
|
||||
private static string MakeFreshdeskApiAuthHeader(TraceWriter log)
|
||||
{
|
||||
var freshdeskApiKey = ConfigurationManager.AppSettings["FreshdeskApiKey"];
|
||||
var b64Creds = Convert.ToBase64String(
|
||||
Encoding.GetEncoding("ISO-8859-1").GetBytes(freshdeskApiKey + ":X"));
|
||||
return b64Creds;
|
||||
}
|
||||
}
|
||||
}
|
88
util/Function/UnblockIp.cs
Normal file
88
util/Function/UnblockIp.cs
Normal file
@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Configuration;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Function.Models;
|
||||
using Microsoft.Azure.WebJobs;
|
||||
using Microsoft.Azure.WebJobs.Host;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Bit.Function
|
||||
{
|
||||
public static class UnblockIp
|
||||
{
|
||||
[FunctionName("UnblockIp")]
|
||||
public static void Run(
|
||||
[QueueTrigger("unblockip", Connection = "")]string myQueueItem,
|
||||
TraceWriter log)
|
||||
{
|
||||
log.Info($"C# Queue trigger function processed: {myQueueItem}");
|
||||
UnblockIpAsync(myQueueItem, log).Wait();
|
||||
}
|
||||
|
||||
private static async Task UnblockIpAsync(string id, TraceWriter log)
|
||||
{
|
||||
if(id == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var zoneId = ConfigurationManager.AppSettings["ZoneId"];
|
||||
var xAuthEmail = ConfigurationManager.AppSettings["X-Auth-Email"];
|
||||
var xAuthKey = ConfigurationManager.AppSettings["X-Auth-Key"];
|
||||
|
||||
if(id.Contains(".") || id.Contains(":"))
|
||||
{
|
||||
// IP address messages.
|
||||
using(var client = new HttpClient())
|
||||
{
|
||||
client.BaseAddress = new Uri("https://api.cloudflare.com");
|
||||
client.DefaultRequestHeaders.Accept.Clear();
|
||||
client.DefaultRequestHeaders.Add("X-Auth-Email", xAuthEmail);
|
||||
client.DefaultRequestHeaders.Add("X-Auth-Key", xAuthKey);
|
||||
|
||||
var response = await client.GetAsync($"/client/v4/zones/{zoneId}/firewall/access_rules/rules?" +
|
||||
$"configuration_target=ip&configuration_value={id}");
|
||||
|
||||
var responseString = await response.Content.ReadAsStringAsync();
|
||||
var responseJson = JsonConvert.DeserializeObject<ListResult>(responseString);
|
||||
|
||||
if(!responseJson.Success)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach(var rule in responseJson.Result)
|
||||
{
|
||||
if(rule.Configuration?.Value != id)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
log.Info($"Unblock IP {id}, {rule.Id}");
|
||||
await DeleteRuleAsync(zoneId, xAuthEmail, xAuthKey, rule.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
log.Info($"Unblock Id {id}");
|
||||
await DeleteRuleAsync(zoneId, xAuthEmail, xAuthKey, id);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task DeleteRuleAsync(string zoneId, string xAuthEmail, string xAuthKey, string id)
|
||||
{
|
||||
var path = $"/client/v4/zones/{zoneId}/firewall/access_rules/rules/{id}";
|
||||
using(var client = new HttpClient())
|
||||
{
|
||||
client.BaseAddress = new Uri("https://api.cloudflare.com");
|
||||
client.DefaultRequestHeaders.Accept.Clear();
|
||||
client.DefaultRequestHeaders.Add("X-Auth-Email", xAuthEmail);
|
||||
client.DefaultRequestHeaders.Add("X-Auth-Key", xAuthKey);
|
||||
await client.DeleteAsync(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
2
util/Function/host.json
Normal file
2
util/Function/host.json
Normal file
@ -0,0 +1,2 @@
|
||||
{
|
||||
}
|
7
util/Function/local.settings.json
Normal file
7
util/Function/local.settings.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"IsEncrypted": false,
|
||||
"Values": {
|
||||
"AzureWebJobsStorage": "",
|
||||
"AzureWebJobsDashboard": ""
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user