mirror of
https://github.com/bitwarden/server.git
synced 2025-06-04 02:00:32 -05:00
local attachment storage & docker image
This commit is contained in:
parent
e50b6240e4
commit
fecd5b3a1a
8
attachments/Dockerfile
Normal file
8
attachments/Dockerfile
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
FROM node
|
||||||
|
|
||||||
|
RUN npm install http-server -g
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
COPY entrypoint.sh /
|
||||||
|
RUN chmod +x /entrypoint.sh
|
||||||
|
ENTRYPOINT ["/entrypoint.sh"]
|
7
attachments/build.ps1
Normal file
7
attachments/build.ps1
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
$dir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
|
||||||
|
echo "`n# Building Attachments"
|
||||||
|
|
||||||
|
echo "`nBuilding docker image"
|
||||||
|
docker --version
|
||||||
|
docker build -t bitwarden/attachments $dir\.
|
10
attachments/build.sh
Normal file
10
attachments/build.sh
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
DIR="$(dirname $(readlink -f $0))"
|
||||||
|
|
||||||
|
echo -e "\n# Building Attachments"
|
||||||
|
|
||||||
|
echo -e "\nBuilding docker image"
|
||||||
|
docker --version
|
||||||
|
docker build -t bitwarden/attachments $DIR/.
|
4
attachments/entrypoint.sh
Normal file
4
attachments/entrypoint.sh
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
http-server /etc/bitwarden/core/attachments/. -p 80 -d false --utc
|
||||||
|
|
@ -7,6 +7,9 @@ services:
|
|||||||
web:
|
web:
|
||||||
volumes:
|
volumes:
|
||||||
- /etc/bitwarden/web:/etc/bitwarden/web
|
- /etc/bitwarden/web:/etc/bitwarden/web
|
||||||
|
attachments:
|
||||||
|
volumes:
|
||||||
|
- /etc/bitwarden/core/attachments:/etc/bitwarden/core/attachments
|
||||||
api:
|
api:
|
||||||
volumes:
|
volumes:
|
||||||
- /etc/bitwarden/core:/etc/bitwarden/core
|
- /etc/bitwarden/core:/etc/bitwarden/core
|
||||||
|
@ -7,6 +7,9 @@ services:
|
|||||||
web:
|
web:
|
||||||
volumes:
|
volumes:
|
||||||
- c:/bitwarden/web:/etc/bitwarden/web
|
- c:/bitwarden/web:/etc/bitwarden/web
|
||||||
|
attachments:
|
||||||
|
volumes:
|
||||||
|
- c:/bitwarden/core/attachments:/etc/bitwarden/core/attachments
|
||||||
api:
|
api:
|
||||||
volumes:
|
volumes:
|
||||||
- c:/bitwarden/core:/etc/bitwarden/core
|
- c:/bitwarden/core:/etc/bitwarden/core
|
||||||
|
@ -7,6 +7,9 @@ services:
|
|||||||
web:
|
web:
|
||||||
volumes:
|
volumes:
|
||||||
- c:/bitwarden/web:/etc/bitwarden/web
|
- c:/bitwarden/web:/etc/bitwarden/web
|
||||||
|
attachments:
|
||||||
|
volumes:
|
||||||
|
- c:/bitwarden/core/attachments:/etc/bitwarden/core/attachments
|
||||||
api:
|
api:
|
||||||
volumes:
|
volumes:
|
||||||
- c:/bitwarden/core:/etc/bitwarden/core
|
- c:/bitwarden/core:/etc/bitwarden/core
|
||||||
|
@ -15,6 +15,11 @@ services:
|
|||||||
image: bitwarden/web
|
image: bitwarden/web
|
||||||
container_name: web
|
container_name: web
|
||||||
restart: always
|
restart: always
|
||||||
|
|
||||||
|
attachments:
|
||||||
|
image: bitwarden/attachments
|
||||||
|
container_name: attachments
|
||||||
|
restart: always
|
||||||
|
|
||||||
api:
|
api:
|
||||||
image: bitwarden/api
|
image: bitwarden/api
|
||||||
|
@ -39,6 +39,7 @@
|
|||||||
public class AttachmentSettings
|
public class AttachmentSettings
|
||||||
{
|
{
|
||||||
public string ConnectionString { get; set; }
|
public string ConnectionString { get; set; }
|
||||||
|
public string BaseDirectory { get; set; }
|
||||||
public string BaseUrl { get; set; }
|
public string BaseUrl { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,155 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.IO;
|
||||||
|
using System;
|
||||||
|
using Bit.Core.Models.Table;
|
||||||
|
|
||||||
|
namespace Bit.Core.Services
|
||||||
|
{
|
||||||
|
public class LocalAttachmentStorageService : IAttachmentStorageService
|
||||||
|
{
|
||||||
|
private readonly string _baseDirPath;
|
||||||
|
private readonly string _baseTempDirPath;
|
||||||
|
|
||||||
|
public LocalAttachmentStorageService(
|
||||||
|
GlobalSettings globalSettings)
|
||||||
|
{
|
||||||
|
_baseDirPath = globalSettings.Attachment.BaseDirectory;
|
||||||
|
_baseTempDirPath = $"{_baseDirPath}/temp";
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UploadNewAttachmentAsync(Stream stream, Cipher cipher, string attachmentId)
|
||||||
|
{
|
||||||
|
await InitAsync();
|
||||||
|
var cipherDirPath = $"{_baseDirPath}/{cipher.Id}";
|
||||||
|
CreateDirectoryIfNotExists(cipherDirPath);
|
||||||
|
|
||||||
|
using(var fs = File.Create($"{cipherDirPath}/{attachmentId}"))
|
||||||
|
{
|
||||||
|
stream.Seek(0, SeekOrigin.Begin);
|
||||||
|
stream.CopyTo(fs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UploadShareAttachmentAsync(Stream stream, Guid cipherId, Guid organizationId, string attachmentId)
|
||||||
|
{
|
||||||
|
await InitAsync();
|
||||||
|
var tempCipherOrgDirPath = $"{_baseTempDirPath}/{cipherId}/{organizationId}";
|
||||||
|
CreateDirectoryIfNotExists(tempCipherOrgDirPath);
|
||||||
|
|
||||||
|
using(var fs = File.Create($"{tempCipherOrgDirPath}/{attachmentId}"))
|
||||||
|
{
|
||||||
|
stream.Seek(0, SeekOrigin.Begin);
|
||||||
|
stream.CopyTo(fs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StartShareAttachmentAsync(Guid cipherId, Guid organizationId, string attachmentId)
|
||||||
|
{
|
||||||
|
await InitAsync();
|
||||||
|
var sourceFilePath = $"{_baseTempDirPath}/{cipherId}/{organizationId}/{attachmentId}";
|
||||||
|
if(!File.Exists(sourceFilePath))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var destFilePath = $"{_baseDirPath}/{cipherId}/{attachmentId}";
|
||||||
|
if(!File.Exists(destFilePath))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var originalFilePath = $"{_baseTempDirPath}/{cipherId}/{attachmentId}";
|
||||||
|
DeleteFileIfExists(originalFilePath);
|
||||||
|
|
||||||
|
File.Move(destFilePath, originalFilePath);
|
||||||
|
DeleteFileIfExists(destFilePath);
|
||||||
|
|
||||||
|
File.Move(sourceFilePath, destFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RollbackShareAttachmentAsync(Guid cipherId, Guid organizationId, string attachmentId)
|
||||||
|
{
|
||||||
|
await InitAsync();
|
||||||
|
DeleteFileIfExists($"{_baseTempDirPath}/{cipherId}/{organizationId}/{attachmentId}");
|
||||||
|
|
||||||
|
var originalFilePath = $"{_baseTempDirPath}/{cipherId}/{attachmentId}";
|
||||||
|
if(!File.Exists(originalFilePath))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var destFilePath = $"{_baseDirPath}/{cipherId}/{attachmentId}";
|
||||||
|
DeleteFileIfExists(destFilePath);
|
||||||
|
|
||||||
|
File.Move(originalFilePath, destFilePath);
|
||||||
|
DeleteFileIfExists(originalFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteAttachmentAsync(Guid cipherId, string attachmentId)
|
||||||
|
{
|
||||||
|
await InitAsync();
|
||||||
|
DeleteFileIfExists($"{_baseDirPath}/{cipherId}/{attachmentId}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CleanupAsync(Guid cipherId)
|
||||||
|
{
|
||||||
|
await InitAsync();
|
||||||
|
DeleteDirectoryIfExists($"{_baseTempDirPath}/{cipherId}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteAttachmentsForCipherAsync(Guid cipherId)
|
||||||
|
{
|
||||||
|
await InitAsync();
|
||||||
|
DeleteDirectoryIfExists($"{_baseDirPath}/{cipherId}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteAttachmentsForOrganizationAsync(Guid organizationId)
|
||||||
|
{
|
||||||
|
await InitAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteAttachmentsForUserAsync(Guid userId)
|
||||||
|
{
|
||||||
|
await InitAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeleteFileIfExists(string path)
|
||||||
|
{
|
||||||
|
if(File.Exists(path))
|
||||||
|
{
|
||||||
|
File.Delete(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeleteDirectoryIfExists(string path)
|
||||||
|
{
|
||||||
|
if(Directory.Exists(path))
|
||||||
|
{
|
||||||
|
Directory.Delete(path, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateDirectoryIfNotExists(string path)
|
||||||
|
{
|
||||||
|
if(!Directory.Exists(path))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task InitAsync()
|
||||||
|
{
|
||||||
|
if(!Directory.Exists(_baseDirPath))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(_baseDirPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!Directory.Exists(_baseTempDirPath))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(_baseTempDirPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -268,5 +268,24 @@ namespace Bit.Core.Utilities
|
|||||||
{
|
{
|
||||||
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(obj));
|
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(obj));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool FullFramework()
|
||||||
|
{
|
||||||
|
#if NET461
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool SettingHasValue(string setting)
|
||||||
|
{
|
||||||
|
if(string.IsNullOrWhiteSpace(setting) || setting.Equals("SECRET"))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,9 @@ using Microsoft.AspNetCore.HttpOverrides;
|
|||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
#if NET461
|
||||||
using Microsoft.WindowsAzure.Storage;
|
using Microsoft.WindowsAzure.Storage;
|
||||||
|
#endif
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using SqlServerRepos = Bit.Core.Repositories.SqlServer;
|
using SqlServerRepos = Bit.Core.Repositories.SqlServer;
|
||||||
@ -53,23 +55,38 @@ namespace Bit.Core.Utilities
|
|||||||
{
|
{
|
||||||
services.AddSingleton<IMailService, RazorViewMailService>();
|
services.AddSingleton<IMailService, RazorViewMailService>();
|
||||||
|
|
||||||
if(!string.IsNullOrWhiteSpace(globalSettings.Mail.SendGridApiKey))
|
if(CoreHelpers.SettingHasValue(globalSettings.Mail.SendGridApiKey))
|
||||||
{
|
{
|
||||||
services.AddSingleton<IMailDeliveryService, SendGridMailDeliveryService>();
|
services.AddSingleton<IMailDeliveryService, SendGridMailDeliveryService>();
|
||||||
}
|
}
|
||||||
|
else if(CoreHelpers.SettingHasValue(globalSettings.Mail?.Smtp?.Host) &&
|
||||||
|
CoreHelpers.SettingHasValue(globalSettings.Mail?.Smtp?.Username) &&
|
||||||
|
CoreHelpers.SettingHasValue(globalSettings.Mail?.Smtp?.Password))
|
||||||
|
{
|
||||||
|
services.AddSingleton<IMailDeliveryService, SmtpMailDeliveryService>();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
services.AddSingleton<IMailDeliveryService, NoopMailDeliveryService>();
|
services.AddSingleton<IMailDeliveryService, NoopMailDeliveryService>();
|
||||||
}
|
}
|
||||||
|
|
||||||
#if NET461
|
#if NET461
|
||||||
services.AddSingleton<IPushNotificationService, NotificationHubPushNotificationService>();
|
if(globalSettings.SelfHosted)
|
||||||
services.AddSingleton<IPushRegistrationService, NotificationHubPushRegistrationService>();
|
{
|
||||||
|
services.AddSingleton<IPushNotificationService, NoopPushNotificationService>();
|
||||||
|
services.AddSingleton<IPushRegistrationService, NoopPushRegistrationService>();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
services.AddSingleton<IPushNotificationService, NotificationHubPushNotificationService>();
|
||||||
|
services.AddSingleton<IPushRegistrationService, NotificationHubPushRegistrationService>();
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
services.AddSingleton<IPushNotificationService, NoopPushNotificationService>();
|
services.AddSingleton<IPushNotificationService, NoopPushNotificationService>();
|
||||||
services.AddSingleton<IPushRegistrationService, NoopPushRegistrationService>();
|
services.AddSingleton<IPushRegistrationService, NoopPushRegistrationService>();
|
||||||
#endif
|
#endif
|
||||||
if(!string.IsNullOrWhiteSpace(globalSettings.Storage.ConnectionString))
|
|
||||||
|
if(CoreHelpers.SettingHasValue(globalSettings.Storage.ConnectionString))
|
||||||
{
|
{
|
||||||
services.AddSingleton<IBlockIpService, AzureQueueBlockIpService>();
|
services.AddSingleton<IBlockIpService, AzureQueueBlockIpService>();
|
||||||
}
|
}
|
||||||
@ -78,10 +95,14 @@ namespace Bit.Core.Utilities
|
|||||||
services.AddSingleton<IBlockIpService, NoopBlockIpService>();
|
services.AddSingleton<IBlockIpService, NoopBlockIpService>();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!string.IsNullOrWhiteSpace(globalSettings.Attachment.ConnectionString))
|
if(CoreHelpers.SettingHasValue(globalSettings.Attachment.ConnectionString))
|
||||||
{
|
{
|
||||||
services.AddSingleton<IAttachmentStorageService, AzureAttachmentStorageService>();
|
services.AddSingleton<IAttachmentStorageService, AzureAttachmentStorageService>();
|
||||||
}
|
}
|
||||||
|
else if(CoreHelpers.SettingHasValue(globalSettings.Attachment.BaseDirectory))
|
||||||
|
{
|
||||||
|
services.AddSingleton<IAttachmentStorageService, LocalAttachmentStorageService>();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
services.AddSingleton<IAttachmentStorageService, NoopAttachmentStorageService>();
|
services.AddSingleton<IAttachmentStorageService, NoopAttachmentStorageService>();
|
||||||
@ -169,8 +190,8 @@ namespace Bit.Core.Utilities
|
|||||||
{
|
{
|
||||||
identityServerBuilder.AddTemporarySigningCredential();
|
identityServerBuilder.AddTemporarySigningCredential();
|
||||||
}
|
}
|
||||||
else if(!string.IsNullOrWhiteSpace(globalSettings.IdentityServer.CertificatePassword) &&
|
else if(!string.IsNullOrWhiteSpace(globalSettings.IdentityServer.CertificatePassword)
|
||||||
System.IO.File.Exists("identity.pfx"))
|
&& File.Exists("identity.pfx"))
|
||||||
{
|
{
|
||||||
var identityServerCert = CoreHelpers.GetCertificate("identity.pfx",
|
var identityServerCert = CoreHelpers.GetCertificate("identity.pfx",
|
||||||
globalSettings.IdentityServer.CertificatePassword);
|
globalSettings.IdentityServer.CertificatePassword);
|
||||||
|
@ -204,6 +204,16 @@ server {{
|
|||||||
proxy_redirect off;
|
proxy_redirect off;
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
location /attachments/ {{
|
||||||
|
proxy_pass http://attachments/;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Url-Scheme $scheme;
|
||||||
|
proxy_redirect off;
|
||||||
|
}}
|
||||||
|
|
||||||
location /api/ {{
|
location /api/ {{
|
||||||
proxy_pass http://api/;
|
proxy_pass http://api/;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
@ -243,6 +253,8 @@ globalSettings:baseServiceUri:api={_url}/api
|
|||||||
globalSettings:baseServiceUri:identity={_url}/identity
|
globalSettings:baseServiceUri:identity={_url}/identity
|
||||||
globalSettings:sqlServer:connectionString={dbConnectionString}
|
globalSettings:sqlServer:connectionString={dbConnectionString}
|
||||||
globalSettings:identityServer:certificatePassword={_identityCertPassword}
|
globalSettings:identityServer:certificatePassword={_identityCertPassword}
|
||||||
|
globalSettings:attachment:baseDirectory=/etc/bitwarden/core/attachments
|
||||||
|
globalSettings:attachment:baseUrl={_url}/attachments
|
||||||
globalSettings:duo:aKey={Helpers.SecureRandomString(32, alpha: true, numeric: true)}
|
globalSettings:duo:aKey={Helpers.SecureRandomString(32, alpha: true, numeric: true)}
|
||||||
globalSettings:yubico:clientId=REPLACE
|
globalSettings:yubico:clientId=REPLACE
|
||||||
globalSettings:yubico:REPLACE");
|
globalSettings:yubico:REPLACE");
|
||||||
@ -265,6 +277,8 @@ SA_PASSWORD={dbPass}");
|
|||||||
sw.Write($@"var bitwardenAppSettings = {{
|
sw.Write($@"var bitwardenAppSettings = {{
|
||||||
apiUri: ""{_url}/api"",
|
apiUri: ""{_url}/api"",
|
||||||
identityUri: ""{_url}/identity"",
|
identityUri: ""{_url}/identity"",
|
||||||
|
stripeKey: null,
|
||||||
|
braintreeKey: null,
|
||||||
whitelistDomains: [""{_domain}""]
|
whitelistDomains: [""{_domain}""]
|
||||||
}};");
|
}};");
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user