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 user policies #3820

Merged
merged 5 commits into from
Apr 27, 2017
Merged

Temp keys user policies #3820

merged 5 commits into from
Apr 27, 2017

Conversation

chenriksson
Copy link
Member

Adding support for user security policies, including policies for use of temp keys and min client version

Onboarding and auditing will be separate PRs

@@ -293,6 +294,7 @@ private HttpStatusCodeWithBodyResult VerifyPackageKeyInternal(User user, Credent
[RequireSsl]
[ApiAuthorize]
[ApiScopeRequired(NuGetScopes.PackagePush, NuGetScopes.PackagePushVersion)]
[SecurityPolicy(SecurityPolicyAction.PackagePush)]
Copy link
Contributor

Choose a reason for hiding this comment

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

consider merging SecurityPolicy attribute with ApiAuthorize attribute.

builder.RegisterType<SecurityPolicyService>()
.AsSelf()
.As<ISecurityPolicyService>()
.InstancePerLifetimeScope();
Copy link
Contributor

@skofman1 skofman1 Apr 25, 2017

Choose a reason for hiding this comment

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

Any reason for ISecurityPolicyService not to be a single instance (SingleInstance())?

Copy link
Member

Choose a reason for hiding this comment

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

I think we should prefer InstancePerLifetimeScope over SuntingleInstance until we have a caching/perf/behavior requirement for it. It's easier for unintended interaction between requests to occur with SingleInstance


In reply to: 113238685 [](ancestors = 113238685)

/// </summary>
public class SecurityPolicyService : ISecurityPolicyService
{
public IEnumerable<UserSecurityPolicyHandler> UserPolicyHandlers { get; private set; }
Copy link
Contributor

Choose a reason for hiding this comment

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

consider making this a static

@skofman1
Copy link
Contributor

// Copyright (c) .NET Foundation. All rights reserved.

null checks


Refers to: src/NuGetGallery/Security/UserSecurityPolicyContext.cs:1 in d018112. [](commit_id = d018112, deletion_comment = False)

.WillCascadeOnDelete(true);

modelBuilder.Entity<UserSecurityPolicy>()
.HasKey(p => p.Key);
Copy link
Contributor

Choose a reason for hiding this comment

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

Is not this duplication of the line 123.

Copy link
Member Author

Choose a reason for hiding this comment

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

Removed

@skofman1
Copy link
Contributor

Looks great! 👍

private Version GetMaxOfMinClientVersions(UserSecurityPolicyContext context)
{
var policyStates = context.Policies.Select(p => JsonConvert.DeserializeObject<State>(p.Value));
return policyStates.Max(s => s.MinClientVersion);
Copy link
Contributor

Choose a reason for hiding this comment

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

Can be nulls among these values? In this case will the Max throw?

Copy link
Member Author

Choose a reason for hiding this comment

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

Shouldn't be null, but added logic to guard against it

var clientVersionString = context.HttpContext.Request?.Headers[Constants.ClientVersionHeaderName];

Version clientVersion;
return Version.TryParse(clientVersionString, out clientVersion) ? clientVersion : null;
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we want to log in AI the null case? The Evaluate below will return a non success result, the user scenario may be impacted in this case.

Copy link
Member Author

Choose a reason for hiding this comment

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

Instead of logging in individual handlers, I'm planning to add telemetry and auditing to the SecurityPolicyService in a separate PR. AI can log any policy failures and include username, policy name and value.

Copy link
Contributor

Choose a reason for hiding this comment

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

If that will be in a different PR is ok.

if (clientVersion == null || clientVersion < minClientVersion)
{
return new SecurityPolicyResult(false, string.Format(CultureInfo.CurrentCulture,
Strings.SecurityPolicy_RequireMinClientVersionForPush, minClientVersion));
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we want to log in AI the fact that the clientVersion < minClientVersion?

{
[JsonProperty("v")]
[JsonConverter(typeof(VersionConverter))]
public Version MinClientVersion { get; set; }
Copy link
Member

Choose a reason for hiding this comment

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

Version [](start = 19, length = 7)

should be a NuGetVersion, really

/// </summary>
public abstract class UserSecurityPolicyHandler
{
public string Name { get; private set; }
Copy link
Member

Choose a reason for hiding this comment

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

private [](start = 34, length = 7)

can remove private set;
This is readonly right?

/// <summary>
/// Whether security policy criteria was successfully met.
/// </summary>
public bool Success { get; private set; }
Copy link
Member

Choose a reason for hiding this comment

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

private [](start = 35, length = 7)

can remove private set; as this is readonly

/// <summary>
/// Type name for the policy handler that provides policy behavior.
/// </summary>
[Required]
Copy link
Member

Choose a reason for hiding this comment

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

Required [](start = 9, length = 8)

Can' this [Required] attribute be defined in EntitiesContext instead? Seems like you are requiring User.

Copy link
Member Author

Choose a reason for hiding this comment

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

HasRequired in EntitiesContext is for a required relationship. I added Required here since it's the only non-nullable column that isn't a PK/FK. Not sure how this actually gets used, but we don't seem to be consistent with [Required] usage.

/// <summary>
/// Security policy service.
/// </summary>
public ISecurityPolicyService SecurityPolicyService { get; private set; }
Copy link
Member

Choose a reason for hiding this comment

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

ISecurityPolicyService [](start = 15, length = 22)

why public?

[InlineData("4.1.0")]
[InlineData("3.0.0")]
[InlineData("2.0.0,4.1.0")]
public void EvaluateReturnsFailureIfClientVersionLower(string minClientVersions)
Copy link
Member

Choose a reason for hiding this comment

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

EvaluateReturnsFailureIfClientVersionLower [](start = 20, length = 42)

client version is a NuGetVersion not a Version so we should test a couple NuGetVersion variants as well

  • 4.1.0-beta1

@joelverhagen
Copy link
Member

🕐

/// Type name for the policy handler that provides policy behavior.
/// </summary>
[Required]
public string Name { get; set; }
Copy link
Member

Choose a reason for hiding this comment

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

Name [](start = 22, length = 4)

I think type name is an implementation detail. We should have a hard coded constant for this (either an int which is consistent with the rest of the DB or const string).

Copy link
Member Author

Choose a reason for hiding this comment

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

Policy handler names are now constants. I left as strings which I think will be easier to read when querying the DB. This PR only reads policies, but the next PR (onboarding) will start writing them.

public class RequirePackageVerifyScopePolicy : UserSecurityPolicyHandler
{
public RequirePackageVerifyScopePolicy()
: base(nameof(RequirePackageVerifyScopePolicy), SecurityPolicyAction.PackageVerify)
Copy link
Member

Choose a reason for hiding this comment

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

RequirePackageVerifyScopePolicy [](start = 26, length = 31)

Please use a const string or const int for this. nameof is easy to change with refactoring tools

Copy link
Member

Choose a reason for hiding this comment

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

I see you have unit tests for this so we aren't blind to the change, but I think we should be a bit more explicit about this string. It could even be a public const string property on this class.


In reply to: 113264288 [](ancestors = 113264288)

{
public static SecurityPolicyResult SuccessResult = new SecurityPolicyResult(true, null);

public SecurityPolicyResult(bool success, string errorMessage)
Copy link
Member

Choose a reason for hiding this comment

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

SecurityPolicyResult [](start = 15, length = 20)

seems like success = true and errorMessage != null is an invalid state, right? I like making the ctor private in this case and having a CreateErrorResult(string errorMessage) method.

yield return new RequireMinClientVersionForPushPolicy();
yield return new RequirePackageVerifyScopePolicy();
}

Copy link
Member

Choose a reason for hiding this comment

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

nit: remove extra line

public class RequirePackageVerifyScopePolicy : UserSecurityPolicyHandler
{
public RequirePackageVerifyScopePolicy()
: base(nameof(RequirePackageVerifyScopePolicy), SecurityPolicyAction.PackageVerify)
Copy link
Member

Choose a reason for hiding this comment

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

SecurityPolicyAction [](start = 60, length = 20)

Maybe I am missing it but where are we asserting that RequirePackageVerifyScopePolicy has action PackageVerify and not something else?

@joelverhagen
Copy link
Member

Functional test(s)?

@chenriksson
Copy link
Member Author

@joelverhagen I need to create onboarded test account for the functional test. Am thinking of doing onboarding in next PR, then auditing/telemetry, then functional test.

@joelverhagen
Copy link
Member

Please make sure the deployment task mentions running EF migrations.

@chenriksson chenriksson merged commit 53e74ae into dev Apr 27, 2017
@scottbommarito scottbommarito mentioned this pull request Apr 27, 2017
22 tasks
@chenriksson chenriksson deleted the chenriks-tempkeys-policies branch April 27, 2017 14:23
// Resolve the policy service if security policy checks are required.
if (SecurityPolicyAction.HasValue)
{
SecurityPolicyService = ((AppController)filterContext.Controller)?.GetService<ISecurityPolicyService>();
Copy link
Contributor

Choose a reason for hiding this comment

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

This will throw if the ApiAuthorizeAttribute is used on a controller that doesn't extend AppController, right? I would recommend that you add documentation that this attribute must be used on controllers that extend AppController. Also, maybe throw an exception if the controller isn't an AppController to make this easier to debug if this issue ever comes up?
*

var policyStates = context.Policies
.Where(p => !string.IsNullOrEmpty(p.Value))
.Select(p => JsonConvert.DeserializeObject<State>(p.Value));
return policyStates.Max(s => s.MinClientVersion);
Copy link
Contributor

Choose a reason for hiding this comment

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

Any particular reason for the policyStates variable? You could merge the two statements into one and return its result directly.

/// <returns></returns>
public static SecurityPolicyResult CreateErrorResult(string errorMessage)
{
if (string.IsNullOrEmpty(errorMessage))
Copy link
Contributor

Choose a reason for hiding this comment

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

string.IsNullOrEmpty seems a little too strong. Would a null check be better here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants