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

ClientsCertificate feature spec #8823

Merged
merged 21 commits into from
Feb 22, 2020
Merged

ClientsCertificate feature spec #8823

merged 21 commits into from
Feb 22, 2020

Conversation

BlackGad
Copy link
Contributor

Added draft specification accordingly to request

Copy link
Member

@zivkan zivkan left a comment

Choose a reason for hiding this comment

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

Thank you for creating the spec. I really appreciate the effort put into this non-trivial and important feature.

I had a bit of a look, and please keep in mind that it's early in the morning here so maybe I'm not quite fully awake yet.

I think there are two things missing from the spec.

  1. Are HTTPS client certs per-source, or sent to all sources automatically, or only sent after getting a 401, like the current auth workflow? What happens if I have two sources that each need a different https client cert?

  2. Pretty much everything in the nuget.config file can be edited with nuget.exe commands, for example nuget config -set .., nuget sources add ... This spec should propose commands to add, list, edit and delete client certificate config.

designs/NuGet-ClientCertificates.md Outdated Show resolved Hide resolved
designs/NuGet-ClientCertificates.md Outdated Show resolved Hide resolved
designs/NuGet-ClientCertificates.md Outdated Show resolved Hide resolved
designs/NuGet-ClientCertificates.md Outdated Show resolved Hide resolved
Can be configured with 2 Add item's and item text body

- Key="**Path**" Value="Absolute or relative path to certificate file". Relative path resolve have 2 stages: first relative to configuration file origin, second relative to current application directory.
- Optional Key="**Password**" Value="Encrypted password". Password for certificate file. Encrypted in same manner as PackageSourceCredential password.
Copy link
Member

Choose a reason for hiding this comment

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

What happens if the certificate needs a password or hardware key to unlock, and the password is not specified in the config? Will entering the password be supported by an interactive mode restore?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Current implementation will fail with error on certificate load. It is bit complicated to me introduce such logic with out help.

designs/NuGet-ClientCertificates.md Outdated Show resolved Hide resolved
designs/NuGet-ClientCertificates.md Outdated Show resolved Hide resolved

## Configuration components implementation

Added several new SettingItem's to parse configuration above. Certificates search logic encapsulated inside.
Copy link
Member

Choose a reason for hiding this comment

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

Implementations are supposed to implement a spec, not the other way around. Either this is too specific information for a spec, or if the point is to propose the new APIs, then it should read like a proposal and if it changes, the PR will have to change to meet the spec.

designs/NuGet-ClientCertificates.md Outdated Show resolved Hide resolved

## NuGet Cli implementation as example

1) Internally certificates extracted from configuration items on base [Command.Execute](https://github.com/NuGet/NuGet.Client/blob/d4f53c3e523493fcbe35c537cb004e9a3e228abd/src/NuGet.Clients/NuGet.CommandLine/Commands/Command.cs) with `ClientCertificateProvider.Provide` method
Copy link
Member

Choose a reason for hiding this comment

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

again, the spec is supposed to be the "source of truth", so if there's a bug, or if someone decides to rewrite the client from scratch, the specs should contain the information required. The spec shouldn't refer back to implementations, or PRs, to specify how the feature works.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Plaese, look at new version. Is it better?

Added clarification for fromCert body certificate format
Changed configuration accordingly to implementation changes
@BlackGad
Copy link
Contributor Author

  1. Are HTTPS client certs per-source, or sent to all sources automatically, or only sent after getting a 401, like the current auth workflow? What happens if I have two sources that each need a different https client cert?

From my experience and tests HttpClientHandler internally pick up most suitable client certificate. Ignores them if client certificate not required.

Inline PEM certificates description change
Extended and reworked a bit
@BlackGad
Copy link
Contributor Author

Pretty much everything in the nuget.config file can be edited with nuget.exe commands, for example nuget config -set .., nuget sources add ... This spec should propose commands to add, list, edit and delete client certificate config.

@zivkan added implementation. Described new command in new version of spec


## NuGet configuration changes

Provide new configuration section `clientCertificates` which may have children of 3 types:
Copy link
Member

Choose a reason for hiding this comment

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

I was busy last week and didn't have a chance to look at this again, but I quickly checked the existing client credentials configuration, and think client certs could fit in there:

<configuration>
  <packageSourceCredentials>
    <source1>
      <add key='UserName' value='test_user' />
      <add key='ClearTextPassword' value='test_password' />
    </source1>
    <source2>
      <add key='CertStore' value='cert:\CurrentUser\My' />
      <add key='FindByThumbprint' value='4894671ae5aa84840cc1079e89e82d426bc24ec6' />
    </source2>
    <source3>
      <add key='CertFile' value='certificate.pfx' />
      <add key='ClearTextPassword' value='...' />
    </source3>
    <source4>
      <add key='CertPEM' value='-----BEGIN CERTIFICATE-----
MIIEjjCCA3agAwIBAgIJIBkBGf8AAAAPMA0GCSqGSIb3DQEBCwUAMIGzMQswCQYD
...
tJl1UvF7GWJd0yNyPVqCCnBY
-----END CERTIFICATE-----' />
    </source4>
  </packageSourceCredentials>
</configuration>

I don't think we have a schema for the nuget.config file, but if we did, this would avoid needing to change it. Certs that need passwords could use the existing password and clear text password to specify the cert password. There would be no ambiguity about when different sources need different certs (and it will be easier to avoid "leaking" cert info to sources that don't need client certs).

I think this would be a better user experience. @nkolev92 @anangaur @rrelyea what do you think?

@zivkan
Copy link
Member

zivkan commented Nov 27, 2019

@BlackGad FYI, the NuGet client team had a first spec review about this. As is common in my experience with the first review of any spec, we came away with more questions than answers. If you'd like to be part of future spec reviews, let me know and we'll try to include you. Keep in mind the client team is on the west coast of the US, so any meetings will be during business hours there. Also many people take vacation in December (myself included), so there may be additional delays during the next month.

The key points from the meeting:

  • Check with security experts

I need to contact internal security experts to find out their opinions/recommendations about HTTPS client certificates in general. Some specific points to check: Is it acceptable to store private key passwords in configuration files (encrypted only, or plain text ok?). Are embedded PEM certificates ok? (someone believes that Azure used to use PEM certifcates for auth, but stopped. need more details if it's security related)

  • settings persistence

There was a preference for re-using the packageSourceCredentials element, rather than creating a new section. HTTP auth can technically be mixed with SSL client certs, so our settings should allow one source to use both. This means no overlaoded keys (so cert password needs to be different to http basic auth password).

  • certificate passwords

Need to support interactive mode for command line operations, for everything that already supports interactive mode (restore/push. maybe dotnet add package?). Need to consider whether NuGet's credential plugin model should also support https client certs, and if so how it all works. For example, if a Windows Hello PIN certificate is used, does the credential provider pass the PIN to NuGet? If face unlock, or hardware 2FA is needed, how does it all work?

  • nuget.exe config commands

if we go ahead with using packageSourceCredentials for storage, we'll need to redesign the commands to change the config. Do we keep a different command for certs and username/password creds, or do we have a single nuget sources command which sets both username/password and cert creds?

@BlackGad
Copy link
Contributor Author

First, thanks for your response, appreciate it :)

HTTP auth can technically be mixed with SSL client certs

My opinion is that packageSourceCredentials logically differs from clientCertificates. packageSourceCredentials responsible for authorization and clientCertificates for transport level security. Of course internal implementation may be similar or same but for user it must be different.

If you'd like to be part of future spec reviews, let me know and we'll try to include you. Keep in mind the client team is on the west coast of the US, so any meetings will be during business hours there.

Can you specify the approximate time of reviews?

Need to consider whether NuGet's credential plugin model should also support https client certs, and if so how it all works. For example, if a Windows Hello PIN certificate is used, does the credential provider pass the PIN to NuGet? If face unlock, or hardware 2FA is needed, how does it all work.

Client certificates required for push/install/list operations (operations that interact with remote sources). This feature not related to any credential stuff because it is transport level security(lower logical level like TCP comparing to HTTP). Please, do not try to artificially connect them.

Also I insist on generic client certificate configuration (not linked to concrete source). In case of linking - NuGet configuration structure and internal implementation complexity will be increased. HttpClientHandler already has logic (has stood the test of time) for mapping client certificates to appropriate endpoints.

Example:

  1. Decision to use client certificates to secure organization servers
  2. Configure internal package source from team 1 (automatically requires personal client certificate)
  3. Configure internal package source from team 2 (automatically requires personal client certificate)
  4. Team 3 needs to use both package sources

So in case of client certificate linked to specific source you need to specify it 2 times. Or to simplify configuration create same thing that NuGet already has with package source configs (sections packageSources, disabledPackageSources, activePackageSource and so one). packageSourceCredentials has even worse situation because of packageSources keys transformation from attribute value to XML node name with escaping.

I think configuration must be simple and understandable. Additional linked config sections contravene to this.

@zivkan
Copy link
Member

zivkan commented Nov 27, 2019

My opinion is that packageSourceCredentials logically differs from clientCertificates. packageSourceCredentials responsible for authorization and clientCertificates for transport level security. Of course internal implementation may be similar or same but for user it must be different.

It can be, but not always. Before joining the NuGet team I worked on an application using a microservice architecture and HTTPS client certificates were used for both securing the channel and authentication (cert thumbprint gets looked up in an auth database/service to ensure it's whitelisted, but also to find client/user claims, which is used for authorization to certain APIs). Additionally, Azure Active Directory's docs has a section about certificate based authentication. I'm not 100% sure, but I think TFS/Azure DevOps allows this as a mode of authentication, not just transport security. Finally, one customer in the NuGet/Home issue about adding this feature used the word authentication numerous times. While it wasn't clear that they really meant user auth, it also wasn't clear that they only meant TLS. My experience with client certs has been related to client auth, not extra security on the transport layer, so I'm not comfortable making this a solely transport layer concern. One of the former members of the Artifacts Credential Provider asked to be informed when the feature is complete, so clearly they intended to use it as part of the authentication flow (but possibly just for transport security, there wasn't enough information). In an internal email someone from the same team asked if credentials would even be needed if nuget supported mutual ssl, hinting that they might use client cert for auth.

Can you specify the approximate time of reviews?

We'll try to be flexible, but we guess you're in Europe, which means that our morning corresponds to your evening. I think a reasonable number of people might be willing to do a 9am meeting, but I'm not confident that we'll get acceptance for earlier meetings. However, I'll be in Australia all December, and there's no way we can get a suitable time for all 3 locations.

In case of linking - NuGet configuration structure and internal implementation complexity will be increased.

Customers generally don't care about difficulty in implementing when the experience is poor. If we have multiple options with similar customer experiences, then we can decide based on which is easier to implement, but we shouldn't reject good user experiences just because it might be difficult to implement.

So in case of client certificate linked to specific source you need to specify it 2 times.

This is already true for credentials, and I don't remember seeing complaints. nuget.config supports environment variable substitution, so if your concern is having to change values in multiple places when a cert gets renewed, there's already an option to reduce duplication.

@BlackGad
Copy link
Contributor Author

Ok, finally I understood your complaint. I was care about transport security and didn't care about mutual SSL auth. You want to support BOTH features :)

  • FIRST feature: Transport level security

  • SECOND feature: Auth based on client certificates for future usage by source feed servers.

Current solution implements FIRST feature.

To support both I purpose:

  1. Leave implemented clientCertificates configuration section as is.
  2. Introduce one more package source credential child item
<configuration>
  <packageSourceCredentials>
    <source1>
      <add key='Certificate' value='<id of client certificate from clientCertificates section>' />
    </source1>
 </packageSourceCredentials>
</configuration>

Logic for cases

  1. Server requires only TLS security (all current clients). NuGet pass all configured certificates to ClientCertificates property and HttpClientHandler do its magic automatically.
  2. Server may have TLS security AND require specific auth client certificate (future NuGet feed servers). NuGet respect packageSourceCredentials configuration and pass only configured certificates (get link(s) to record(s) from clientCertificates section)

@zivkan
Copy link
Member

zivkan commented Dec 2, 2019

I was trying to create a sample app to validate, but I couldn't get kestrel and httpclient to talk to each other with a client cert. Anyway, my concern about keeping two configurations, one for transport security, another for mutual auth, is that it implies that the two can be used together (one cert for TLS, another for auth). However, the HTTPS protocol only allows a single client certificate to be used (the cert might be in a chain, but two independent certs cannot be used in a single conection). Although HttpClientHandler has a certificate collection, if you look at the APIs to validate the client certificate on Kestrel, you'll see the API only provides a single certificate (and its chain).

So, while keeping the TLS and certificate auth sections separate has the advantage of avoiding needing to repeat the cert information when using multiple sources that use the same cert, it has the disadvantage to being confusing to people who don't know the HTTPS protocol well. Since I was unable to get a demo app working, I wasn't able to test what actually happens, but it looks to me that HttpClient might always use the first certificate, in which case your proposal wouldn't work when using two or more sources that need different certificates. A better case would be that HttpClient tries them one-by-one until one succeeds, but this has multiple potential problems:

  1. If the HTTPS server accepts the cert at connection establishment, but the cert is unauthorised at an application level, there's no way for HttpClient to know to try a different cert.
  2. NuGet will spend extra time establishing and trying to negotiage HTTPS connections using certificates that the nuget.config author knows won't work for the server, thereby slowing down the restore a little.
  3. I still need to find out if potentially "leaking" the public part of the certificate to all NuGet sources is acceptable.

If client certs is only in a per-source configuration, the only disadvantage I can think of is there's less duplication when using multiple sources that use the same cert. I'm getting on a plane soon, so I don't have time to dig into this further now, but so far I'm unconvinced that a global TLS client certificate configuration is a good idea.

@blowdart
Copy link
Member

blowdart commented Dec 3, 2019

I'm the security person mentioned.

I have a concern around the inline certificate as a parameter - this seems to encourage checking the certificate into source control, which is something we'd want to avoid. Given you already have a parameter to use a file in one of the standard formats I am unsure why an inline parameter is necessary, given the risk.

As for separating out certificates, one for HTTPS and one for Auth, that's not really how it works. Certificates are sent during the HTTPS handshake, you don't get to swap them over after that's taken place. You only have one chance to set a certificate. I wouldn't ever rely on HttpClient selecting the right certificate, if certificate auth is in place it should be an explicit selection, rather than hoping the correct one is used.

As for leakage of certificates, that is a concern considering a certificate identifies a user, so it's PII, and now we have GDPR considerations. Certificate parameters should be limited to a single server and not sent to multiple servers by default. A parameter to say "I don't care, splash my certificate everywhere" might be acceptable if it's not on by default.

@BlackGad
Copy link
Contributor Author

BlackGad commented Dec 3, 2019

Ok, so conclusion is: inline certificates definitions must be removed and certificate automatic spreading denied. Am I right?

If so, I think, packageSourceCredentials have no clue with certificates at all.

  1. Will it be enough to add one or more source specifications into clientCertificate section?
    Something like
<configuration>
...
    <clientCertificates>	
        <fromStorage name="unique name 1">
            <!-- Require at least one reference to source -->
            <add key="TargetSource" value="<source name from packageSources section>" />
            ...
            <!-- Optional. CurrentUser by default -->
            <add key="StoreLocation" value="CurrentUser" />
            <!-- Optional. My by default -->
            <add key="StoreName" value="My" />
            <!-- Optional. FindByThumbprint by default -->
            <add key="FindType" value="FindByThumbprint" />
            <!-- Required. -->
            <add key="FindValue" value="4894671ae5aa84840cc1079e89e82d426bc24ec6" />
        </fromStorage>
        <fromFile name="unique name 2">
            <!-- Require at least one reference to source -->
            <add key="TargetSource" value="<source name from packageSources section>" />
            ...
            <!-- Absolute or relative path to certificate file. -->
            <add key="Path" value=".\certificate.pfx" />
            <!-- Encrypted password -->
            <add key="Password" value="..." />
        </fromFile>
    </clientCertificates>
...
</configuration>
  1. Extend client-certificates command with additional required -TargetSource parameter (same multivalue behavior as -CertificateFingerprint has).

  2. Remove FromPEM definition at all.

  3. Remove 'spread' certificates into HttpWebHandler logic

  4. Allow certificate usage after search from settings stage ONLY if single certificate was found.

  5. In future extend 5 behavior for interactive mode. User can choose which certificate to use in case of multiple search results (fromStorage definition can result multiple certificates as result).

@blowdart
Copy link
Member

blowdart commented Dec 4, 2019

That would certainly make me happier :)

@BlackGad
Copy link
Contributor Author

I've updated spec accordingly to latest discussion. Real implementation will be fixed after final spec approving.

@zivkan
Copy link
Member

zivkan commented Jan 5, 2020

Sorry about the delay for the last month.

I don't believe it's an issue any longer since the design changed, but I was concerned that the original proposal to add multiple certs to the HttpClientHandler wouldn't work. Although the API allows multiple certs to be added to the HttpClientHandler's ClientCertificates collection, my test program shows it only ever uses the first cert. Therefore, in order to support using more than one NuGet source that needs an HTTPS client cert, it's critical that each source specifies which cert it needs, so NuGet can configure the HttpClientHandler with only that one certificate. NuGet needs to use a different HttpClientHandler per source, but it already does that anyway.

I'll talk to the PMs over the next week to see if we can get the changes to the config file and the CLI arguments approved, so that the implementation can be unblocked.

@BlackGad
Copy link
Contributor Author

BlackGad commented Jan 7, 2020

No it is not critical :) one certificate search can be specified to multiple sources (I definitely will use such case). It is common that one search may return several valid certificates which differs only by expiration date. Certificates usually renews by domain policies when them still valid (pc cert storage). That's why it is bad idea to artificially limit search count per one source.

So in real life I see it like NuGet configuration under source control inside project which points to search certificate from pc or user cert storage (pc in active directory) and backup local certificate file search for adhoc work (domain controller is unavailable do the different circumstances for a while)
Of course text above is just my opinion :) I definitely do not want to see configuration which looks like authentication section with magic attribute value to node name transformation :)

@zivkan
Copy link
Member

zivkan commented Jan 11, 2020

@BlackGad in case the email address you used in your commit messages is not correct, please contact me (find my email address in any of my commit messages). I scheduled a spec review meeting and sent an invite to what I hope is your email address.

@BlackGad
Copy link
Contributor Author

Hope will join the meeting

@zivkan
Copy link
Member

zivkan commented Jan 14, 2020

We had another meeting for this spec. Special thanks to @BlackGad for joining. Here are the follow up items:

  • We need to decide (more discussions?) about enforcing one certificate per source vs allowing one source to have multiple certificates defined. Should NuGet silently ignore certificates defined in nuget.config that can't be found or should NuGet error out when certs can't be found?

@BlackGad explained his preference about being able to use a single config which "just works" whether at work (where a cert is available in the store), or at home (where a personal certificate file is available on disk, at a location ignored by source control, so that it isn't checked it). @zivkan proposed that instead it should be a one-time configuration in the user profile. A developer's work & home machine can then have different user profile configurations, removing the need to use a single configuration that specified multiple certificates, and therefore each source can have just a single certificate defined.

  • We need to add commands to the dotnet cli in addition to nuget.exe for modifying nuget.config's new options for client certs. They should follow the pattern from the dotnet sources spec for argument order (nuget sources list vs dotnet nuget list sources).

  • Should dotnet nuget list sources list certificates (and credentials?) per source, thereby removing the need for a dotnet nuget list certificates command? While this is more of a topic for the dotnet nuget sources spec, it does impact what needs to be defined here.

  • Add nuget client-certificate update command, and return an error if nuget client-certificate add is used with a cert name that already exists, instead of implicitly updating, to be consistent with nuget source add & nuget source update.

edit: forgot one

  • @zivkan had concerns over the -Check argument proposed. Is there really value in a "I don't know if this command is going to work, so don't fail if it doesn't"? Or am I misunderstanding the point?


- `-Path` `string` - Path to certificate file added to a file client certificate source.

- `-TargetSource` `string` - Required semicolon separated option to specify NuGet sources on which client certificate will be applied.
Copy link
Member

Choose a reason for hiding this comment

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

I didn't think of it during the meeting, but one problem with using semicolons on a CLI is that some shells use the semicolon to separate multiple commands written on a single line. Therefore, if we use semicolon as the separator, it increases the risk that customers will accidentally run unintended commands. While the commands would likely fail and therefore not have a large risk of causing damage, it might be better to change the design to use another separator that reduces the risk of bad commands being run accidentally.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Extend client-certificates command with additional required -TargetSource parameter (same multivalue behavior as -CertificateFingerprint has).

It is not mine invention )

Copy link
Member

Choose a reason for hiding this comment

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

Can a source name contain a semicolon?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Depends on how NuGet internal NuGet.exe command line parser works. Just reused existing API.

@zivkan
Copy link
Member

zivkan commented Jan 14, 2020

Here's a little pros and cons table I came up with to compare allowing a single certificate to be assigned to multiple sources vs requiring each source to have a single certificate definition, even if it's to a certificate used by another source.

Scenario Multiple certs Single cert
Developers work on multiple machines, using different certificates (from store at work, from file at home) (1) ✔ Check in single config to repo, listing multiple cert locations ❌ once-off configuration per machine
A cert used by multiple sources changes (changed password, change thumbprint, etc) ✔ Single place to modify (either single command, or single manual edit) ❌ multiple configuration changes necessary (multiple commands or multiple edits to config file)
Modify which sources use a certificate ❌ specialist command to manage certs in config file needed ✔ use credentials pattern of extra parameter to existing sources management command
User doesn't have required certificate(s) available ❌ Silently ignore certificate(s) not found and see an SSL/TLS connection error ✔ Clear error message saying certificate was not found
Configuration file ❌ cert configuration needs a name used for nothing more than referencing the cert in the configuration commands ✔ Certificate config can be uniquely identified by source name, which already exists
  1. This scenario is complicated and conditions on the scenario can be adapted to make either design look better than the other. The tick and cross given to the single and multiple certificate designs are for the best-case scenario in favour of the multiple-certificate design, where there is only a single repository, and all developers working from home use a certificate file saved in the same location and using the same password (which sounds like a security problem to me). If different developers have different cert passwords, then the multiple-cert design has no advantage over the single cert design. If the developers work on multiple repositories, there may also be a one-time configuration per repository to set up the certificate file in the correct location, or use a user-profile config, the same as what they'd do in a single certificate design.

Regarding the second scenario when a cert changes, a simple find and replace makes it easy to update a configuration file that lists a single certificate multiple times. Even if the customer is using a CLI to modify the command, they can use their shell's command buffer to go back to the last command and modify the source name, without having to re-type the whole command. I personally don't think this is much of a downside, especially considering how infrequently certs usually expire.

After writing this comment, honestly I feel more strongly that a single certificate definition per source design is better, as I think we could simplify some things, mainly the CLI commands to modify the config file.

Updated -Check parameter description
@heng-liu
Copy link
Contributor

Here's a little pros and cons table I came up with to compare allowing a single certificate to be assigned to multiple sources vs requiring each source to have a single certificate definition, even if it's to a certificate used by another source.

Scenario
Multiple certs
Single cert

Developers work on multiple machines, using different certificates (from store at work, from file at home) (1)
✔ Check in single config to repo, listing multiple cert locations
❌ once-off configuration per machine

A cert used by multiple sources changes (changed password, change thumbprint, etc)
✔ Single place to modify (either single command, or single manual edit)
❌ multiple configuration changes necessary (multiple commands or multiple edits to config file)

Modify which sources use a certificate
❌ specialist command to manage certs in config file needed
✔ use credentials pattern of extra parameter to existing sources management command

User doesn't have required certificate(s) available
❌ Silently ignore certificate(s) not found and see an SSL/TLS connection error
✔ Clear error message saying certificate was not found

Configuration file
❌ cert configuration needs a name used for nothing more than referencing the cert in the configuration commands
✔ Certificate config can be uniquely identified by source name, which already exists

This scenario is complicated and conditions on the scenario can be adapted to make either design look better than the other. The tick and cross given to the single and multiple certificate designs are for the best-case scenario in favour of the multiple-certificate design, where there is only a single repository, and all developers working from home use a certificate file saved in the same location and using the same password (which sounds like a security problem to me). If different developers have different cert passwords, then the multiple-cert design has no advantage over the single cert design. If the developers work on multiple repositories, there may also be a one-time configuration per repository to set up the certificate file in the correct location, or use a user-profile config, the same as what they'd do in a single certificate design.

Regarding the second scenario when a cert changes, a simple find and replace makes it easy to update a configuration file that lists a single certificate multiple times. Even if the customer is using a CLI to modify the command, they can use their shell's command buffer to go back to the last command and modify the source name, without having to re-type the whole command. I personally don't think this is much of a downside, especially considering how infrequently certs usually expire.
After writing this comment, honestly I feel more strongly that a single certificate definition per source design is better, as I think we could simplify some things, mainly the CLI commands to modify the config file.

Here's a little pros and cons table I came up with to compare allowing a single certificate to be assigned to multiple sources vs requiring each source to have a single certificate definition, even if it's to a certificate used by another source.

Scenario
Multiple certs
Single cert

Developers work on multiple machines, using different certificates (from store at work, from file at home) (1)
✔ Check in single config to repo, listing multiple cert locations
❌ once-off configuration per machine

A cert used by multiple sources changes (changed password, change thumbprint, etc)
✔ Single place to modify (either single command, or single manual edit)
❌ multiple configuration changes necessary (multiple commands or multiple edits to config file)

Modify which sources use a certificate
❌ specialist command to manage certs in config file needed
✔ use credentials pattern of extra parameter to existing sources management command

User doesn't have required certificate(s) available
❌ Silently ignore certificate(s) not found and see an SSL/TLS connection error
✔ Clear error message saying certificate was not found

Configuration file
❌ cert configuration needs a name used for nothing more than referencing the cert in the configuration commands
✔ Certificate config can be uniquely identified by source name, which already exists

This scenario is complicated and conditions on the scenario can be adapted to make either design look better than the other. The tick and cross given to the single and multiple certificate designs are for the best-case scenario in favour of the multiple-certificate design, where there is only a single repository, and all developers working from home use a certificate file saved in the same location and using the same password (which sounds like a security problem to me). If different developers have different cert passwords, then the multiple-cert design has no advantage over the single cert design. If the developers work on multiple repositories, there may also be a one-time configuration per repository to set up the certificate file in the correct location, or use a user-profile config, the same as what they'd do in a single certificate design.

Regarding the second scenario when a cert changes, a simple find and replace makes it easy to update a configuration file that lists a single certificate multiple times. Even if the customer is using a CLI to modify the command, they can use their shell's command buffer to go back to the last command and modify the source name, without having to re-type the whole command. I personally don't think this is much of a downside, especially considering how infrequently certs usually expire.
After writing this comment, honestly I feel more strongly that a single certificate definition per source design is better, as I think we could simplify some things, mainly the CLI commands to modify the config file.

I think it's better to have a single certificate for a source.
In my opinion, it's more natural to add a certificate as an attribute of source, as I suppose some users (especially who are new to this feature) will notice certificates only when the source is not available. And the "single certificate for a source" seems more friendly to them (the 4th senario).
By the way, shall we consider giving the right error message when:

  1. no cert is specified in source, but actually it's needed.
    2.cert specified in source, but it's not available
    3.cert specified in source, but it's invalid.

@BlackGad
Copy link
Contributor Author

@heng-liu you mean attribute in package source(1) or client certificate configuration attribute (reference to source)(2)

In case of 1:

<configuration>
...
    <packageSources>
        <add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
        <add key="Contoso" value="https://contoso.com/packages/"  clientCertificate="<unique name 1>" />
    </packageSources>
    <clientCertificates>	
        <fromStorage name="unique name 1">
        ...
        </fromStorage>
    </clientCertificates>
...
</configuration>

In case of 2:

<configuration>
...
    <packageSources>
        <add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
        <add key="Contoso" value="https://contoso.com/packages/" />
    </packageSources>
    <clientCertificates>	
        <fromStorage name="Contoso">
        ...
        </fromStorage>
    </clientCertificates>
...
</configuration>

From this 2 I prefer second one (because of separated configuration that will not affect existing packageSources implementation)

In general as far as I see NuGet team leaning towards one certificate per one source solution. For me it is not crucial to have one to many relations :) So will change specification a bit to satisfy requirements.

@BlackGad
Copy link
Contributor Author

I have updated specification accordingly to our latest discussion. Please take a look when will have time.

@karann-msft
Copy link
Contributor

The spec should call out error scenarios and the associated error messages.

@zivkan
Copy link
Member

zivkan commented Jan 23, 2020

Just had another meeting with @karann-msft and @rrelyea. What we'd like to see before signing off on the spec:

  • Each source can have at most one client-cert.
  • When different nuget.config files specify a client cert for a source, the cert configured should be overridden like like any other config value that gets overridden depending on nuget.config path heirarchy. If a nuget.config file lists two different client certs for a single source, that's a configuration error.
  • When a client cert is configured, but the certificate can't be found, report an error (so restore should fail, even if the source doesn't need the client cert)
  • We think the nuget.config syntax should be changed to be a bit more like client trust policy configuration. Here's the updated sample:
    <clientCerts>	
        <storeCert packageSource="Contoso"
                    storeLocation="CurrentUser"
                    storeName="My"
                    findBy="thumbprint"
                    findValue="4894671ae5aa84840cc1079e89e82d426bc24ec6" />
        <fileCert packageSource="Foo"
                  path=".\certificate.pfx"
                  password="..." />
    </clientCerts>

Key difference that I remember I doing:
* clientCerts, rather than the longer name
* storeCert and fileCert instead of the from* names
* XML attributes, rather than the add elements
* findBy, rather than FindType, and remove the FindBy from the enum values.

  • commands to modify the configuration should be added to the dotnet CLI, rather than nuget.exe. The commands should be dotnet nuget add client-cert ..., dotnet nuget update client-cert, dotnet nuget remove client-cert, dotnet nuget list client-cert. @rrelyea is working on nuget sources: move implementation into NuGet.Commands.dll - refactor into SourcesArgs/SourcesRunner - move enums/strings NuGet.Client#3112 and possibly another dotnet nuget * source PR. Wait until his work is merged before working on the implementation of dotnet nuget * client-cert commands.

    • this also means that commands should use the unix style --arg syntax, rather than -Arg powershell syntax.
  • cert validation should always be performed when adding/updating, making the -check or --check command redundant. If someone wants to add a cert to their config when the cert doesn't currently exist, they should hand-edit their config file.

  • if there are other known error scenarios, they should be listed. For example, what if the server doesn't accept the client cert.

@nkolev92
Copy link
Member

nkolev92 commented Jan 23, 2020

I already reacted on the comment, but wanted to write a message of approval too :)

  • Being in sync with current standards in nuget.config is great.
  • Fail fast for configuration scenarios is in line with the rest of our approaches.

@BlackGad
Copy link
Contributor Author

BlackGad commented Feb 3, 2020

Spec was updated.

Some remarks to @zivkan comment:

Each source can have at most one client-cert.

One configuration is fine but it can return several valid certificates accordingly to search criteria. And it is valid situation in case of overlapped expiration date strategy. So it is reasonable to use any (first) certificate from search.

When a client cert is configured, but the certificate can't be found, report an error (so restore should fail, even if the source doesn't need the client cert)

Imagine that domain controller fail and automatic client certificate enrollment did not happened. You have PC configuration with additional package source (secured with certificate ofc). Now imagine you want to create new project which will have no dependencies from custom packages (only official one). Studio will show error end deny any future development?

It is better to warn user not deny.

cert validation should always be performed when adding/updating, making the -check or --check command redundant. If someone wants to add a cert to their config when the cert doesn't currently exist, they should hand-edit their config file.

Disagree. On initial PC setup we use batch commands with sequential choco nuget installation and nuget PC configuration. Admin does not care where NuGet physically stores its configuration file, he wants setup environment via console with batch commands.

Also new PC can be connected to domain after initial configuration where automatic certificates enrollment feature active.

Personally I expecting warning message that certificate does not exist rather than error message and link to huge tutorial where you describe physical nuget configuration file location on selected environment (windows/linux/per user) and big configuration file syntax specification.

@BlackGad
Copy link
Contributor Author

BlackGad commented Feb 3, 2020

Also like new configuration syntax you purposed 👍

designs/NuGet-ClientCertificates.md Outdated Show resolved Hide resolved
designs/NuGet-ClientCertificates.md Outdated Show resolved Hide resolved
designs/NuGet-ClientCertificates.md Outdated Show resolved Hide resolved
designs/NuGet-ClientCertificates.md Outdated Show resolved Hide resolved
designs/NuGet-ClientCertificates.md Show resolved Hide resolved
@zivkan
Copy link
Member

zivkan commented Feb 5, 2020

When a client cert is configured, but the certificate can't be found, report an error (so restore should fail, even if the source doesn't need the client cert)

Imagine that domain controller fail and automatic client certificate enrollment did not happened. You have PC configuration with additional package source (secured with certificate ofc). Now imagine you want to create new project which will have no dependencies from custom packages (only official one). Studio will show error end deny any future development?

There's two things here. First, your question is does NuGet fail a restore when one source cannot be contacted, even if no packages are needed from that source? The answer is yes, that's how it works right now. There is an issue about it, but it's out of scope of this spec to change that behaviour.

So, given we know the restore is going to fail anyway, failing fast minimises the chance that the customer walks away from the computer after restore starts (huge solutions can have hundreds of packages and take minutes to complete), only to come back to find it failed, so they have to fix the issue and run another restore. Failing fast is a common pattern in systems development.

cert validation should always be performed when adding/updating, making the -check or --check command redundant. If someone wants to add a cert to their config when the cert doesn't currently exist, they should hand-edit their config file.

Disagree. On initial PC setup we use batch commands with sequential choco nuget installation and nuget PC configuration. Admin does not care where NuGet physically stores its configuration file, he wants setup environment via console with batch commands.

Also new PC can be connected to domain after initial configuration where automatic certificates enrollment feature active.

Personally I expecting warning message that certificate does not exist rather than error message and link to huge tutorial where you describe physical nuget configuration file location on selected environment (windows/linux/per user) and big configuration file syntax specification.

The CLI tools are primarily aimed at interactive usage. Working in scripts is a good idea, so I'd be more open to a --noValidation, or --noCheck option. But the defaults should optimise for the best interactive experience, and comply with the fail-fast principal. Additionally, we try not to leave projects or configs in a broken state. If we know that a restore will fail immediately after changing the config, we shouldn't save the config change. For example, dotnet add package packageThatDoesNotExist will not modify the csproj.

@karann-msft @rrelyea what do you think?

@BlackGad
Copy link
Contributor Author

BlackGad commented Feb 6, 2020

The CLI tools are primarily aimed at interactive usage. Working in scripts is a good idea, so I'd be more open to a --noValidation, or --noCheck option. But the defaults should optimise for the best interactive experience, and comply with the fail-fast principal

Purposing name this flag --force.

@BlackGad
Copy link
Contributor Author

Updated accordingly to discussion

@zivkan
Copy link
Member

zivkan commented Feb 18, 2020

@NuGet/nuget-client last chance to review otherwise I'll merge it after giving it another pass myself.

@nkolev92
Copy link
Member

I'll take a look today.

Copy link
Member

@nkolev92 nkolev92 left a comment

Choose a reason for hiding this comment

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

Beautiful. 👏

Thanks @BlackGad

@BlackGad
Copy link
Contributor Author

Ok, I will start new implementation accordingly this specification. It will be easier to start from scratch

Copy link
Member

@zivkan zivkan left a comment

Choose a reason for hiding this comment

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

@BlackGad thank you for your patience while we got the spec sorted out.

Also, just a little reminder that @rrelyea recently checked in some changes to make dotnet nuget [add|remove|...] source, which I believe also affected nuget.exe. So, the way to add commands to both the dotnet cli and nuget cli will be different now compared to your previous efforts. Hopefully it's now easier to get new commands to work in both places.

@zivkan zivkan merged commit 46d01ba into NuGet:dev Feb 22, 2020
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.

8 participants