Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Temp keys telemetry and auditing #3945

Merged
merged 12 commits into from
May 16, 2017
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace NuGetGallery.Auditing.AuditedEntities
{
/// <summary>
/// Auditing details for UserSecurityPolicy entity.
/// </summary>
public class AuditedUserSecurityPolicy
{
public string Name { get; }
public string Subscription { get; }
public string Value { get; }

public AuditedUserSecurityPolicy(UserSecurityPolicy policy)
{
Name = policy.Name;
Subscription = policy.Subscription;
Value = policy.Value;
}
}
}
3 changes: 1 addition & 2 deletions src/NuGetGallery.Core/Auditing/AuditedPackageAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ public enum AuditedPackageAction
Unlist,
Edit,
UndoEdit,


Verify
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shims will need to be updated as well

}
}
11 changes: 11 additions & 0 deletions src/NuGetGallery.Core/Auditing/AuditedSecurityPolicyAction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace NuGetGallery.Auditing
{
public enum AuditedSecurityPolicyAction
{
Create,
Verify
}
}
4 changes: 3 additions & 1 deletion src/NuGetGallery.Core/Auditing/AuditedUserAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ public enum AuditedUserAction
ChangeEmail,
CancelChangeEmail,
ConfirmEmail,
Login
Login,
SubscribeToPolicies,
UnsubscribeFromPolicies
}
}
16 changes: 0 additions & 16 deletions src/NuGetGallery.Core/Auditing/UserAuditAction.cs

This file was deleted.

19 changes: 18 additions & 1 deletion src/NuGetGallery.Core/Auditing/UserAuditRecord.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NuGetGallery.Auditing.AuditedEntities;

namespace NuGetGallery.Auditing
{
Expand All @@ -17,6 +18,11 @@ public class UserAuditRecord : AuditRecord<AuditedUserAction>
public CredentialAuditRecord[] AffectedCredential { get; }
public string AffectedEmailAddress { get; }

/// <summary>
/// Subset of user policies affected by the action (subscription / unsubscription).
/// </summary>
public AuditedUserSecurityPolicy[] AffectedPolicies { get; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Be sure to update shims.


public UserAuditRecord(User user, AuditedUserAction action)
: this(user, action, Enumerable.Empty<Credential>())
{
Expand Down Expand Up @@ -55,7 +61,18 @@ public UserAuditRecord(User user, AuditedUserAction action, string affectedEmail
{
AffectedEmailAddress = affectedEmailAddress;
}


public UserAuditRecord(User user, AuditedUserAction action, IEnumerable<UserSecurityPolicy> affectedPolicies)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add tests for this constructor in UserAuditRecordTests.cs.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

: this(user, action, Enumerable.Empty<Credential>())
{
if (affectedPolicies == null || affectedPolicies.Count() == 0)
{
throw new ArgumentException(nameof(affectedPolicies));
}

AffectedPolicies = affectedPolicies.Select(p => new AuditedUserSecurityPolicy(p)).ToArray();
}

public override string GetPath()
{
return Username.ToLowerInvariant();
Expand Down
50 changes: 50 additions & 0 deletions src/NuGetGallery.Core/Auditing/UserSecurityPolicyAuditRecord.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Linq;
using NuGetGallery.Auditing.AuditedEntities;

namespace NuGetGallery.Auditing
{
/// <summary>
/// Audit record for failed user security policy evaluations.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

failed and succeeded policies evaluation

/// </summary>
public class UserSecurityPolicyAuditRecord : AuditRecord<AuditedSecurityPolicyAction>
{
public string Username { get; }

public AuditedUserSecurityPolicy[] AffectedPolicies { get; }

public bool Success { get; set; }

public string ErrorMessage { get; }

public UserSecurityPolicyAuditRecord(string username,
AuditedSecurityPolicyAction action,
IEnumerable<UserSecurityPolicy> affectedPolicies,
bool success, string errorMessage = null)
:base(action)
{
if (string.IsNullOrEmpty(username))
{
throw new ArgumentNullException(nameof(username));
}
if (affectedPolicies == null || affectedPolicies.Count() == 0)
{
throw new ArgumentException(nameof(affectedPolicies));
}

Username = username;
AffectedPolicies = affectedPolicies.Select(p => new AuditedUserSecurityPolicy(p)).ToArray();
Success = success;
ErrorMessage = errorMessage;
}

public override string GetPath()
{
return Username.ToLowerInvariant();
}
}
}
4 changes: 3 additions & 1 deletion src/NuGetGallery.Core/NuGetGallery.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,14 @@
<Compile Include="Auditing\AuditedEntities\AuditedPackage.cs" />
<Compile Include="Auditing\AuditedEntities\AuditedPackageIdentifier.cs" />
<Compile Include="Auditing\AuditedAuthenticatedOperationAction.cs" />
<Compile Include="Auditing\AuditedEntities\AuditedUserSecurityPolicy.cs" />
<Compile Include="Auditing\AuditedSecurityPolicyAction.cs" />
<Compile Include="Auditing\AuditEntry.cs" />
<Compile Include="Auditing\AuditActor.cs" />
<Compile Include="Auditing\AuditingService.cs" />
<Compile Include="Auditing\AuditRecord.cs" />
<Compile Include="Auditing\FailedAuthenticatedOperationAuditRecord.cs" />
<Compile Include="Auditing\UserSecurityPolicyAuditRecord.cs" />
<Compile Include="Auditing\FileSystemAuditingService.cs" />
<Compile Include="Auditing\CloudAuditingService.cs" />
<Compile Include="Auditing\CredentialAuditRecord.cs" />
Expand All @@ -139,7 +142,6 @@
<Compile Include="Auditing\AuditedUserAction.cs" />
<Compile Include="Auditing\PackageAuditRecord.cs" />
<Compile Include="Auditing\ScopeAuditRecord.cs" />
<Compile Include="Auditing\UserAuditAction.cs" />
<Compile Include="Auditing\UserAuditRecord.cs" />
<Compile Include="CoreConstants.cs" />
<Compile Include="CredentialTypes.cs" />
Expand Down
4 changes: 4 additions & 0 deletions src/NuGetGallery/App_Start/DefaultDependenciesModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,10 @@ protected override void Load(ContainerBuilder builder)
.As<ISecurityPolicyService>()
.InstancePerLifetimeScope();

builder.RegisterType<SecurePushSubscription>()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason not to have it as a SingleInstance() ? Same for SecurityPolicyService. The more we use singleton, the cheaper a request will be.

.AsSelf()
.InstancePerLifetimeScope();

var mailSenderThunk = new Lazy<IMailSender>(
() =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

using System;
using System.Globalization;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.Owin;
Expand Down
41 changes: 32 additions & 9 deletions src/NuGetGallery/Controllers/ApiController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
using NuGetGallery.Filters;
using NuGetGallery.Infrastructure.Authentication;
using NuGetGallery.Packaging;
using NuGetGallery.Security;
using PackageIdValidator = NuGetGallery.Packaging.PackageIdValidator;

namespace NuGetGallery
Expand All @@ -47,6 +48,7 @@ public partial class ApiController
public ITelemetryService TelemetryService { get; set; }
public AuthenticationService AuthenticationService { get; set; }
public ICredentialBuilder CredentialBuilder { get; set; }
protected ISecurityPolicyService SecurityPolicyService { get; set; }

protected ApiController()
{
Expand All @@ -69,7 +71,8 @@ public ApiController(
IGalleryConfigurationService configurationService,
ITelemetryService telemetryService,
AuthenticationService authenticationService,
ICredentialBuilder credentialBuilder)
ICredentialBuilder credentialBuilder,
ISecurityPolicyService securityPolicies)
{
EntitiesContext = entitiesContext;
PackageService = packageService;
Expand All @@ -87,6 +90,7 @@ public ApiController(
TelemetryService = telemetryService;
AuthenticationService = authenticationService;
CredentialBuilder = credentialBuilder;
SecurityPolicyService = securityPolicies;
StatisticsService = null;
}

Expand All @@ -107,8 +111,11 @@ public ApiController(
IGalleryConfigurationService configurationService,
ITelemetryService telemetryService,
AuthenticationService authenticationService,
ICredentialBuilder credentialBuilder)
: this(entitiesContext, packageService, packageFileService, userService, nugetExeDownloaderService, contentService, indexingService, searchService, autoCuratePackage, statusService, messageService, auditingService, configurationService, telemetryService, authenticationService, credentialBuilder)
ICredentialBuilder credentialBuilder,
ISecurityPolicyService securityPolicies)
: this(entitiesContext, packageService, packageFileService, userService, nugetExeDownloaderService, contentService,
indexingService, searchService, autoCuratePackage, statusService, messageService, auditingService,
configurationService, telemetryService, authenticationService, credentialBuilder, securityPolicies)
{
StatisticsService = statisticsService;
}
Expand Down Expand Up @@ -224,15 +231,21 @@ public async virtual Task<ActionResult> CreatePackageVerificationKeyAsync(string

[HttpGet]
[RequireSsl]
[ApiAuthorize(SecurityPolicyAction.PackageVerify)]
[ApiAuthorize]
[ApiScopeRequired(NuGetScopes.PackageVerify, NuGetScopes.PackagePush, NuGetScopes.PackagePushVersion)]
[ActionName("VerifyPackageKey")]
public async virtual Task<ActionResult> VerifyPackageKeyAsync(string id, string version)
{
var policyResult = await SecurityPolicyService.EvaluateAsync(SecurityPolicyAction.PackageVerify, HttpContext);
if (!policyResult.Success)
{
return new HttpStatusCodeWithBodyResult(HttpStatusCode.BadRequest, policyResult.ErrorMessage);
}

var user = GetCurrentUser();
var credential = user.GetCurrentApiKeyCredential(User.Identity);

var result = VerifyPackageKeyInternal(user, credential, id, version);
var result = await VerifyPackageKeyInternalAsync(user, credential, id, version);

// Expire and delete verification key after first use to avoid growing the database tables.
if (CredentialTypes.IsPackageVerificationApiKey(credential.Type))
Expand All @@ -245,7 +258,7 @@ public async virtual Task<ActionResult> VerifyPackageKeyAsync(string id, string
return (ActionResult)result ?? new EmptyResult();
}

private HttpStatusCodeWithBodyResult VerifyPackageKeyInternal(User user, Credential credential, string id, string version)
private async Task<HttpStatusCodeWithBodyResult> VerifyPackageKeyInternalAsync(User user, Credential credential, string id, string version)
{
// Verify that the user has permission to push for the specific Id \ version combination.
var package = PackageService.FindPackageByIdAndVersion(id, version);
Expand All @@ -254,7 +267,11 @@ private HttpStatusCodeWithBodyResult VerifyPackageKeyInternal(User user, Credent
return new HttpStatusCodeWithBodyResult(
HttpStatusCode.NotFound, String.Format(CultureInfo.CurrentCulture, Strings.PackageWithIdAndVersionNotFound, id, version));
}


// Write an audit record
await AuditingService.SaveAuditRecordAsync(
new PackageAuditRecord(package, AuditedPackageAction.Verify, "Verified via API."));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the string "Verified via API." needed? The action implies that already. Consider removing.


if (!package.IsOwner(user))
{
return new HttpStatusCodeWithBodyResult(HttpStatusCode.Forbidden, Strings.ApiKeyNotAuthorized);
Expand Down Expand Up @@ -282,7 +299,7 @@ private HttpStatusCodeWithBodyResult VerifyPackageKeyInternal(User user, Credent

[HttpPut]
[RequireSsl]
[ApiAuthorize(SecurityPolicyAction.PackagePush)]
[ApiAuthorize]
[ApiScopeRequired(NuGetScopes.PackagePush, NuGetScopes.PackagePushVersion)]
[ActionName("PushPackageApi")]
public virtual Task<ActionResult> CreatePackagePut()
Expand All @@ -292,7 +309,7 @@ public virtual Task<ActionResult> CreatePackagePut()

[HttpPost]
[RequireSsl]
[ApiAuthorize(SecurityPolicyAction.PackagePush)]
[ApiAuthorize]
[ApiScopeRequired(NuGetScopes.PackagePush, NuGetScopes.PackagePushVersion)]
[ActionName("PushPackageApi")]
public virtual Task<ActionResult> CreatePackagePost()
Expand All @@ -302,6 +319,12 @@ public virtual Task<ActionResult> CreatePackagePost()

private async Task<ActionResult> CreatePackageInternal()
{
var policyResult = await SecurityPolicyService.EvaluateAsync(SecurityPolicyAction.PackagePush, HttpContext);
if (!policyResult.Success)
{
return new HttpStatusCodeWithBodyResult(HttpStatusCode.BadRequest, policyResult.ErrorMessage);
}

// Get the user
var user = GetCurrentUser();

Expand Down
Loading