1
0
mirror of https://github.com/bitwarden/server synced 2026-01-06 02:23:51 +00:00

[SM-1293] Add endpoint to fetch secret's access policies (#4146)

* Add authz handling for secret access policy reads

* Add the ability to fetch secret access polices from the repository

* refactor response models

* Add new endpoint
This commit is contained in:
Thomas Avery
2024-06-07 12:08:38 -05:00
committed by GitHub
parent a1d609b208
commit 36705790ad
17 changed files with 554 additions and 143 deletions

View File

@@ -47,6 +47,9 @@ public class SecretAuthorizationHandler : AuthorizationHandler<SecretOperationRe
case not null when requirement == SecretOperations.Delete:
await CanDeleteSecretAsync(context, requirement, resource);
break;
case not null when requirement == SecretOperations.ReadAccessPolicies:
await CanReadAccessPoliciesAsync(context, requirement, resource);
break;
default:
throw new ArgumentException("Unsupported operation requirement type provided.", nameof(requirement));
}
@@ -152,6 +155,26 @@ public class SecretAuthorizationHandler : AuthorizationHandler<SecretOperationRe
}
}
private async Task CanReadAccessPoliciesAsync(AuthorizationHandlerContext context,
SecretOperationRequirement requirement, Secret resource)
{
var (accessClient, userId) = await _accessClientQuery.GetAccessClientAsync(context.User, resource.OrganizationId);
// Only users and admins can read access policies
if (accessClient != AccessClientType.User && accessClient != AccessClientType.NoAccessCheck)
{
return;
}
var access = await _secretRepository.AccessToSecretAsync(resource.Id, userId, accessClient);
if (access.Write)
{
context.Succeed(requirement);
}
}
private async Task<bool> GetAccessToUpdateSecretAsync(Secret resource, Guid userId, AccessClientType accessClient)
{
var newProject = resource.Projects?.FirstOrDefault();

View File

@@ -20,7 +20,8 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
{
}
public async Task<List<Core.SecretsManager.Entities.BaseAccessPolicy>> CreateManyAsync(List<Core.SecretsManager.Entities.BaseAccessPolicy> baseAccessPolicies)
public async Task<List<Core.SecretsManager.Entities.BaseAccessPolicy>> CreateManyAsync(
List<Core.SecretsManager.Entities.BaseAccessPolicy> baseAccessPolicies)
{
await using var scope = ServiceScopeFactory.CreateAsyncScope();
var dbContext = GetDatabaseContext(scope);
@@ -39,6 +40,13 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
await dbContext.AddAsync(entity);
break;
}
case Core.SecretsManager.Entities.UserSecretAccessPolicy accessPolicy:
{
var entity =
Mapper.Map<UserSecretAccessPolicy>(accessPolicy);
await dbContext.AddAsync(entity);
break;
}
case Core.SecretsManager.Entities.UserServiceAccountAccessPolicy accessPolicy:
{
var entity =
@@ -52,6 +60,12 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
await dbContext.AddAsync(entity);
break;
}
case Core.SecretsManager.Entities.GroupSecretAccessPolicy accessPolicy:
{
var entity = Mapper.Map<GroupSecretAccessPolicy>(accessPolicy);
await dbContext.AddAsync(entity);
break;
}
case Core.SecretsManager.Entities.GroupServiceAccountAccessPolicy accessPolicy:
{
var entity = Mapper.Map<GroupServiceAccountAccessPolicy>(accessPolicy);
@@ -65,6 +79,13 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
serviceAccountIds.Add(entity.ServiceAccountId!.Value);
break;
}
case Core.SecretsManager.Entities.ServiceAccountSecretAccessPolicy accessPolicy:
{
var entity = Mapper.Map<ServiceAccountSecretAccessPolicy>(accessPolicy);
await dbContext.AddAsync(entity);
serviceAccountIds.Add(entity.ServiceAccountId!.Value);
break;
}
}
}
@@ -395,6 +416,42 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
await transaction.CommitAsync();
}
public async Task<SecretAccessPolicies?> GetSecretAccessPoliciesAsync(
Guid secretId,
Guid userId)
{
await using var scope = ServiceScopeFactory.CreateAsyncScope();
var dbContext = GetDatabaseContext(scope);
var entities = await dbContext.AccessPolicies.Where(ap =>
((UserSecretAccessPolicy)ap).GrantedSecretId == secretId ||
((GroupSecretAccessPolicy)ap).GrantedSecretId == secretId ||
((ServiceAccountSecretAccessPolicy)ap).GrantedSecretId == secretId)
.Include(ap => ((UserSecretAccessPolicy)ap).OrganizationUser.User)
.Include(ap => ((GroupSecretAccessPolicy)ap).Group)
.Include(ap => ((ServiceAccountSecretAccessPolicy)ap).ServiceAccount)
.Select(ap => new
{
ap,
CurrentUserInGroup = ap is GroupSecretAccessPolicy &&
((GroupSecretAccessPolicy)ap).Group.GroupUsers.Any(g =>
g.OrganizationUser.UserId == userId)
})
.ToListAsync();
if (entities.Count == 0)
{
return null;
}
var organizationId = await dbContext.Secret.Where(s => s.Id == secretId)
.Select(s => s.OrganizationId)
.SingleAsync();
return new SecretAccessPolicies(secretId, organizationId,
entities.Select(e => MapToCore(e.ap, e.CurrentUserInGroup)).ToList());
}
private static async Task UpsertPeoplePoliciesAsync(DatabaseContext dbContext,
List<BaseAccessPolicy> policies, IReadOnlyCollection<AccessPolicy> userPolicyEntities,
IReadOnlyCollection<AccessPolicy> groupPolicyEntities)
@@ -466,13 +523,17 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
baseAccessPolicyEntity switch
{
UserProjectAccessPolicy ap => Mapper.Map<Core.SecretsManager.Entities.UserProjectAccessPolicy>(ap),
GroupProjectAccessPolicy ap => Mapper.Map<Core.SecretsManager.Entities.GroupProjectAccessPolicy>(ap),
ServiceAccountProjectAccessPolicy ap => Mapper
.Map<Core.SecretsManager.Entities.ServiceAccountProjectAccessPolicy>(ap),
UserSecretAccessPolicy ap => Mapper.Map<Core.SecretsManager.Entities.UserSecretAccessPolicy>(ap),
UserServiceAccountAccessPolicy ap =>
Mapper.Map<Core.SecretsManager.Entities.UserServiceAccountAccessPolicy>(ap),
GroupProjectAccessPolicy ap => Mapper.Map<Core.SecretsManager.Entities.GroupProjectAccessPolicy>(ap),
GroupSecretAccessPolicy ap => Mapper.Map<Core.SecretsManager.Entities.GroupSecretAccessPolicy>(ap),
GroupServiceAccountAccessPolicy ap => Mapper
.Map<Core.SecretsManager.Entities.GroupServiceAccountAccessPolicy>(ap),
ServiceAccountProjectAccessPolicy ap => Mapper
.Map<Core.SecretsManager.Entities.ServiceAccountProjectAccessPolicy>(ap),
ServiceAccountSecretAccessPolicy ap => Mapper
.Map<Core.SecretsManager.Entities.ServiceAccountSecretAccessPolicy>(ap),
_ => throw new ArgumentException("Unsupported access policy type")
};
@@ -482,20 +543,26 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
{
Core.SecretsManager.Entities.UserProjectAccessPolicy accessPolicy => Mapper.Map<UserProjectAccessPolicy>(
accessPolicy),
Core.SecretsManager.Entities.UserSecretAccessPolicy accessPolicy => Mapper.Map<UserSecretAccessPolicy>(
accessPolicy),
Core.SecretsManager.Entities.UserServiceAccountAccessPolicy accessPolicy => Mapper
.Map<UserServiceAccountAccessPolicy>(accessPolicy),
Core.SecretsManager.Entities.GroupProjectAccessPolicy accessPolicy => Mapper.Map<GroupProjectAccessPolicy>(
accessPolicy),
Core.SecretsManager.Entities.GroupSecretAccessPolicy accessPolicy => Mapper.Map<GroupSecretAccessPolicy>(
accessPolicy),
Core.SecretsManager.Entities.GroupServiceAccountAccessPolicy accessPolicy => Mapper
.Map<GroupServiceAccountAccessPolicy>(accessPolicy),
Core.SecretsManager.Entities.ServiceAccountProjectAccessPolicy accessPolicy => Mapper
.Map<ServiceAccountProjectAccessPolicy>(accessPolicy),
Core.SecretsManager.Entities.ServiceAccountSecretAccessPolicy accessPolicy => Mapper
.Map<ServiceAccountSecretAccessPolicy>(accessPolicy),
_ => throw new ArgumentException("Unsupported access policy type")
};
}
private Core.SecretsManager.Entities.BaseAccessPolicy MapToCore(
BaseAccessPolicy baseAccessPolicyEntity, bool currentUserInGroup)
BaseAccessPolicy baseAccessPolicyEntity, bool currentUserInGroup)
{
switch (baseAccessPolicyEntity)
{
@@ -505,6 +572,12 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
mapped.CurrentUserInGroup = currentUserInGroup;
return mapped;
}
case GroupSecretAccessPolicy ap:
{
var mapped = Mapper.Map<Core.SecretsManager.Entities.GroupSecretAccessPolicy>(ap);
mapped.CurrentUserInGroup = currentUserInGroup;
return mapped;
}
case GroupServiceAccountAccessPolicy ap:
{
var mapped = Mapper.Map<Core.SecretsManager.Entities.GroupServiceAccountAccessPolicy>(ap);

View File

@@ -517,4 +517,85 @@ public class SecretAuthorizationHandlerTests
Assert.Equal(expected, authzContext.HasSucceeded);
}
[Theory]
[BitAutoData]
public async Task CanReadAccessPolicies_AccessToSecretsManagerFalse_DoesNotSucceed(
SutProvider<SecretAuthorizationHandler> sutProvider, Secret secret,
ClaimsPrincipal claimsPrincipal)
{
var requirement = SecretOperations.ReadAccessPolicies;
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(secret.OrganizationId)
.Returns(false);
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, secret);
await sutProvider.Sut.HandleAsync(authzContext);
Assert.False(authzContext.HasSucceeded);
}
[Theory]
[BitAutoData]
public async Task CanReadAccessPolicies_NullResource_DoesNotSucceed(
SutProvider<SecretAuthorizationHandler> sutProvider, Secret secret,
ClaimsPrincipal claimsPrincipal,
Guid userId)
{
var requirement = SecretOperations.ReadAccessPolicies;
SetupPermission(sutProvider, PermissionType.RunAsAdmin, secret.OrganizationId, userId);
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, null);
await sutProvider.Sut.HandleAsync(authzContext);
Assert.False(authzContext.HasSucceeded);
}
[Theory]
[BitAutoData(AccessClientType.ServiceAccount)]
[BitAutoData(AccessClientType.Organization)]
public async Task CanReadAccessPolicies_UnsupportedClient_DoesNotSucceed(
AccessClientType clientType,
SutProvider<SecretAuthorizationHandler> sutProvider, Secret secret,
ClaimsPrincipal claimsPrincipal)
{
var requirement = SecretOperations.ReadAccessPolicies;
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(secret.OrganizationId)
.Returns(true);
sutProvider.GetDependency<IAccessClientQuery>()
.GetAccessClientAsync(Arg.Any<ClaimsPrincipal>(), secret.OrganizationId)
.Returns((clientType, Guid.NewGuid()));
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, secret);
await sutProvider.Sut.HandleAsync(authzContext);
Assert.False(authzContext.HasSucceeded);
}
[Theory]
[BitAutoData(PermissionType.RunAsAdmin, true, true, true)]
[BitAutoData(PermissionType.RunAsUserWithPermission, false, false, false)]
[BitAutoData(PermissionType.RunAsUserWithPermission, false, true, true)]
[BitAutoData(PermissionType.RunAsUserWithPermission, true, false, false)]
[BitAutoData(PermissionType.RunAsUserWithPermission, true, true, true)]
public async Task CanReadAccessPolicies_AccessCheck(PermissionType permissionType, bool read, bool write,
bool expected,
SutProvider<SecretAuthorizationHandler> sutProvider, Secret secret,
ClaimsPrincipal claimsPrincipal,
Guid userId)
{
var requirement = SecretOperations.ReadAccessPolicies;
SetupPermission(sutProvider, permissionType, secret.OrganizationId, userId);
sutProvider.GetDependency<ISecretRepository>()
.AccessToSecretAsync(secret.Id, userId, Arg.Any<AccessClientType>())
.Returns((read, write));
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, secret);
await sutProvider.Sut.HandleAsync(authzContext);
Assert.Equal(expected, authzContext.HasSucceeded);
}
}