1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-04 01:22:50 -05:00

[SM-394] Secrets Manager (#2164)

Long lived feature branch for Secrets Manager

Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>
Co-authored-by: cd-bitwarden <106776772+cd-bitwarden@users.noreply.github.com>
Co-authored-by: CarleyDiaz-Bitwarden <103955722+CarleyDiaz-Bitwarden@users.noreply.github.com>
Co-authored-by: Thomas Avery <tavery@bitwarden.com>
Co-authored-by: Colton Hurst <colton@coltonhurst.com>
This commit is contained in:
Oscar Hinton
2023-01-13 15:02:53 +01:00
committed by GitHub
parent 09e524c9a2
commit 1f0fc43278
188 changed files with 21346 additions and 329 deletions

View File

@ -2,6 +2,9 @@
using System.Security.Claims;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Identity;
using Bit.Core.IdentityServer;
using Bit.Core.Models.Data;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings;
@ -23,8 +26,8 @@ public class ClientStore : IClientStore
private readonly ICurrentContext _currentContext;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IProviderUserRepository _providerUserRepository;
private readonly IProviderOrganizationRepository _providerOrganizationRepository;
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
private readonly IApiKeyRepository _apiKeyRepository;
public ClientStore(
IInstallationRepository installationRepository,
@ -36,8 +39,8 @@ public class ClientStore : IClientStore
ICurrentContext currentContext,
IOrganizationUserRepository organizationUserRepository,
IProviderUserRepository providerUserRepository,
IProviderOrganizationRepository providerOrganizationRepository,
IOrganizationApiKeyRepository organizationApiKeyRepository)
IOrganizationApiKeyRepository organizationApiKeyRepository,
IApiKeyRepository apiKeyRepository)
{
_installationRepository = installationRepository;
_organizationRepository = organizationRepository;
@ -48,133 +51,219 @@ public class ClientStore : IClientStore
_currentContext = currentContext;
_organizationUserRepository = organizationUserRepository;
_providerUserRepository = providerUserRepository;
_providerOrganizationRepository = providerOrganizationRepository;
_organizationApiKeyRepository = organizationApiKeyRepository;
_apiKeyRepository = apiKeyRepository;
}
public async Task<Client> FindClientByIdAsync(string clientId)
{
if (!_globalSettings.SelfHosted && clientId.StartsWith("installation."))
{
var idParts = clientId.Split('.');
if (idParts.Length > 1 && Guid.TryParse(idParts[1], out Guid id))
{
var installation = await _installationRepository.GetByIdAsync(id);
if (installation != null)
{
return new Client
{
ClientId = $"installation.{installation.Id}",
RequireClientSecret = true,
ClientSecrets = { new Secret(installation.Key.Sha256()) },
AllowedScopes = new string[] { "api.push", "api.licensing", "api.installation" },
AllowedGrantTypes = GrantTypes.ClientCredentials,
AccessTokenLifetime = 3600 * 24,
Enabled = installation.Enabled,
Claims = new List<ClientClaim>
{
new ClientClaim(JwtClaimTypes.Subject, installation.Id.ToString())
}
};
}
}
return await CreateInstallationClientAsync(clientId);
}
else if (_globalSettings.SelfHosted && clientId.StartsWith("internal.") &&
if (_globalSettings.SelfHosted && clientId.StartsWith("internal.") &&
CoreHelpers.SettingHasValue(_globalSettings.InternalIdentityKey))
{
var idParts = clientId.Split('.');
if (idParts.Length > 1)
{
var id = idParts[1];
if (!string.IsNullOrWhiteSpace(id))
{
return new Client
{
ClientId = $"internal.{id}",
RequireClientSecret = true,
ClientSecrets = { new Secret(_globalSettings.InternalIdentityKey.Sha256()) },
AllowedScopes = new string[] { "internal" },
AllowedGrantTypes = GrantTypes.ClientCredentials,
AccessTokenLifetime = 3600 * 24,
Enabled = true,
Claims = new List<ClientClaim>
{
new ClientClaim(JwtClaimTypes.Subject, id)
}
};
}
}
}
else if (clientId.StartsWith("organization."))
{
var idParts = clientId.Split('.');
if (idParts.Length > 1 && Guid.TryParse(idParts[1], out var id))
{
var org = await _organizationRepository.GetByIdAsync(id);
if (org != null)
{
var orgApiKey = (await _organizationApiKeyRepository
.GetManyByOrganizationIdTypeAsync(org.Id, OrganizationApiKeyType.Default))
.First();
return new Client
{
ClientId = $"organization.{org.Id}",
RequireClientSecret = true,
ClientSecrets = { new Secret(orgApiKey.ApiKey.Sha256()) },
AllowedScopes = new string[] { "api.organization" },
AllowedGrantTypes = GrantTypes.ClientCredentials,
AccessTokenLifetime = 3600 * 1,
Enabled = org.Enabled && org.UseApi,
Claims = new List<ClientClaim>
{
new ClientClaim(JwtClaimTypes.Subject, org.Id.ToString())
}
};
}
}
}
else if (clientId.StartsWith("user."))
{
var idParts = clientId.Split('.');
if (idParts.Length > 1 && Guid.TryParse(idParts[1], out var id))
{
var user = await _userRepository.GetByIdAsync(id);
if (user != null)
{
var claims = new Collection<ClientClaim>()
{
new ClientClaim(JwtClaimTypes.Subject, user.Id.ToString()),
new ClientClaim(JwtClaimTypes.AuthenticationMethod, "Application", "external")
};
var orgs = await _currentContext.OrganizationMembershipAsync(_organizationUserRepository, user.Id);
var providers = await _currentContext.ProviderMembershipAsync(_providerUserRepository, user.Id);
var isPremium = await _licensingService.ValidateUserPremiumAsync(user);
foreach (var claim in CoreHelpers.BuildIdentityClaims(user, orgs, providers, isPremium))
{
var upperValue = claim.Value.ToUpperInvariant();
var isBool = upperValue == "TRUE" || upperValue == "FALSE";
claims.Add(isBool ?
new ClientClaim(claim.Key, claim.Value, ClaimValueTypes.Boolean) :
new ClientClaim(claim.Key, claim.Value)
);
}
return new Client
{
ClientId = clientId,
RequireClientSecret = true,
ClientSecrets = { new Secret(user.ApiKey.Sha256()) },
AllowedScopes = new string[] { "api" },
AllowedGrantTypes = GrantTypes.ClientCredentials,
AccessTokenLifetime = 3600 * 1,
ClientClaimsPrefix = null,
Claims = claims
};
}
}
return CreateInternalClient(clientId);
}
return _staticClientStore.ApiClients.ContainsKey(clientId) ?
_staticClientStore.ApiClients[clientId] : null;
if (clientId.StartsWith("organization."))
{
return await CreateOrganizationClientAsync(clientId);
}
if (clientId.StartsWith("user."))
{
return await CreateUserClientAsync(clientId);
}
if (_staticClientStore.ApiClients.ContainsKey(clientId))
{
return _staticClientStore.ApiClients[clientId];
}
return await CreateApiKeyClientAsync(clientId);
}
private async Task<Client> CreateApiKeyClientAsync(string clientId)
{
var apiKey = await _apiKeyRepository.GetDetailsByIdAsync(new Guid(clientId));
if (apiKey == null || apiKey.ExpireAt <= DateTime.Now)
{
return null;
}
var client = new Client
{
ClientId = clientId,
RequireClientSecret = true,
ClientSecrets = { new Secret(apiKey.ClientSecret.Sha256()) },
AllowedScopes = apiKey.GetScopes(),
AllowedGrantTypes = GrantTypes.ClientCredentials,
AccessTokenLifetime = 3600 * 1,
ClientClaimsPrefix = null,
Properties = new Dictionary<string, string> {
{"encryptedPayload", apiKey.EncryptedPayload},
},
Claims = new List<ClientClaim>
{
new(JwtClaimTypes.Subject, apiKey.ServiceAccountId.ToString()),
},
};
switch (apiKey)
{
case ServiceAccountApiKeyDetails key:
client.Claims.Add(new ClientClaim(Claims.Organization, key.ServiceAccountOrganizationId.ToString()));
break;
}
return client;
}
private async Task<Client> CreateUserClientAsync(string clientId)
{
var idParts = clientId.Split('.');
if (idParts.Length <= 1 || !Guid.TryParse(idParts[1], out var id))
{
return null;
}
var user = await _userRepository.GetByIdAsync(id);
if (user == null)
{
return null;
}
var claims = new Collection<ClientClaim>
{
new(JwtClaimTypes.Subject, user.Id.ToString()),
new(JwtClaimTypes.AuthenticationMethod, "Application", "external"),
};
var orgs = await _currentContext.OrganizationMembershipAsync(_organizationUserRepository, user.Id);
var providers = await _currentContext.ProviderMembershipAsync(_providerUserRepository, user.Id);
var isPremium = await _licensingService.ValidateUserPremiumAsync(user);
foreach (var claim in CoreHelpers.BuildIdentityClaims(user, orgs, providers, isPremium))
{
var upperValue = claim.Value.ToUpperInvariant();
var isBool = upperValue is "TRUE" or "FALSE";
claims.Add(isBool
? new ClientClaim(claim.Key, claim.Value, ClaimValueTypes.Boolean)
: new ClientClaim(claim.Key, claim.Value)
);
}
return new Client
{
ClientId = clientId,
RequireClientSecret = true,
ClientSecrets = { new Secret(user.ApiKey.Sha256()) },
AllowedScopes = new[] { "api" },
AllowedGrantTypes = GrantTypes.ClientCredentials,
AccessTokenLifetime = 3600 * 1,
ClientClaimsPrefix = null,
Claims = claims,
};
}
private async Task<Client> CreateOrganizationClientAsync(string clientId)
{
var idParts = clientId.Split('.');
if (idParts.Length <= 1 || !Guid.TryParse(idParts[1], out var id))
{
return null;
}
var org = await _organizationRepository.GetByIdAsync(id);
if (org == null)
{
return null;
}
var orgApiKey = (await _organizationApiKeyRepository
.GetManyByOrganizationIdTypeAsync(org.Id, OrganizationApiKeyType.Default))
.First();
return new Client
{
ClientId = $"organization.{org.Id}",
RequireClientSecret = true,
ClientSecrets = { new Secret(orgApiKey.ApiKey.Sha256()) },
AllowedScopes = new[] { ApiScopes.ApiOrganization },
AllowedGrantTypes = GrantTypes.ClientCredentials,
AccessTokenLifetime = 3600 * 1,
Enabled = org.Enabled && org.UseApi,
Claims = new List<ClientClaim>
{
new(JwtClaimTypes.Subject, org.Id.ToString()),
},
};
}
private Client CreateInternalClient(string clientId)
{
var idParts = clientId.Split('.');
if (idParts.Length <= 1)
{
return null;
}
var id = idParts[1];
if (string.IsNullOrWhiteSpace(id))
{
return null;
}
return new Client
{
ClientId = $"internal.{id}",
RequireClientSecret = true,
ClientSecrets = { new Secret(_globalSettings.InternalIdentityKey.Sha256()) },
AllowedScopes = new[] { ApiScopes.Internal },
AllowedGrantTypes = GrantTypes.ClientCredentials,
AccessTokenLifetime = 3600 * 24,
Enabled = true,
Claims = new List<ClientClaim>
{
new(JwtClaimTypes.Subject, id),
},
};
}
private async Task<Client> CreateInstallationClientAsync(string clientId)
{
var idParts = clientId.Split('.');
if (idParts.Length <= 1 || !Guid.TryParse(idParts[1], out Guid id))
{
return null;
}
var installation = await _installationRepository.GetByIdAsync(id);
if (installation == null)
{
return null;
}
return new Client
{
ClientId = $"installation.{installation.Id}",
RequireClientSecret = true,
ClientSecrets = { new Secret(installation.Key.Sha256()) },
AllowedScopes = new[]
{
ApiScopes.ApiPush,
ApiScopes.ApiLicensing,
ApiScopes.ApiInstallation,
},
AllowedGrantTypes = GrantTypes.ClientCredentials,
AccessTokenLifetime = 3600 * 24,
Enabled = installation.Enabled,
Claims = new List<ClientClaim>
{
new(JwtClaimTypes.Subject, installation.Id.ToString()),
},
};
}
}