-
Notifications
You must be signed in to change notification settings - Fork 1
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
Adds LicensingService
#40
base: main
Are you sure you want to change the base?
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #40 +/- ##
==========================================
+ Coverage 37.88% 43.14% +5.25%
==========================================
Files 38 41 +3
Lines 1127 1335 +208
Branches 99 123 +24
==========================================
+ Hits 427 576 +149
- Misses 666 715 +49
- Partials 34 44 +10 ☔ View full report in Codecov by Sentry. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I love the direction change and the requisite changes. It'll be work in server
to use and that's its only implementation, but it makes me wonder about where else we'd engage this. Could it be used for our independent components like Key Connector, and we support that in the creation and validation routines as "products", or are those just claims?
I would like to understand the backstory with appsettings.SelfHosted.json
and if we could indeed drop it entirely.
Besides a few to-dos this seems good to merge to me.
ArgumentNullException.ThrowIfNull(logger); | ||
ArgumentNullException.ThrowIfNull(internalLicensingOptions); | ||
|
||
// TODO: Do we need to support runtime changes to these settings at all, I don't think we do... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ℹ️ No.
public async Task<IEnumerable<Claim>> VerifyLicenseAsync(string license) | ||
{ | ||
ArgumentNullException.ThrowIfNull(license); | ||
// TODO: Should we validate that this is self host? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ℹ️ I'd say no. I can make up some ideas around how we'd offer license verification capabilities somewhere new so we don't need to constrain this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sweet, I concur.
} | ||
|
||
// Should I even take a ClaimsIdentity and return it here instead of a list of claims? | ||
return tokenValidationResult.ClaimsIdentity.Claims; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ℹ️ I took a look and don't see anything else we'd want to return really.
/// </summary> | ||
bool IsCloud { get; } | ||
|
||
// TODO: Any other options than valid for? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💭 Limit the product names enabled perhaps? Made up constraint by me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I personally don't want to limit the product names in anyway, if we did it would require that we update this library whenever we create a new product, which feels like an artificial slowdown. It would also do nothing to stop one product from using another products name. But I do think that maybe taking the product name in this method could be a good idea. Right now, this has a limitation of creating licenses for only a single products licenses per project. Maybe that is a limitation we don't want. Just adding the product name here though would still have all products share the same signing certificate which maybe we'd want that configurable per product too.
But forcing the product name to be passed in here does force them to configure it, vs now I allow it to be configured but grab the IHostEnvironment.ApplicationName
(csproj name) which could have someone rename it one day without knowing the ramifications.
Also, I think I am going back on accepting a TimeSpan
now and think it should be a DateTime
. Most licenses are going to be valid for longer than a month, most likely in terms of years and their aren't TimeSpan.FromYears
overloads (because a year is variable based on when you start it), likely expiration date is going to come from something like stripe instead of being something you can hardcode and I'm pretty sure it will come from stripe as a DateTime. Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not opposed to updating this when a product is added -- that's a major event that requires preparation. That said, it has pitfalls and I am just making this up so we don't need to add it; if we want to license differently in the future we can figure it out then.
Renaming a .csproj
seems a bit weird to me and I figure we don't need to account for it.
I like the idea of allowing a set time to be the input instead. Let the caller figure out when they want it to expire.
@@ -0,0 +1,177 @@ | |||
using System.Reflection; | |||
using System.Security.Cryptography.X509Certificates; | |||
using Azure.Storage.Blobs; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💭 I cringe a bit that we're adding this large dependency to the library and hope it has some reuse.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, same. This is partly why I want to make sure this library is trimmable so that the parts you don't need could be trimmed away but the alternative I have been considering is turning this into an SDK. So instead of configuring what you need through BitwardenHostOptions
you do it through MSBuild properties like <EnableCloudLicensingSupport>true</EnableCloudLicensingSupport>
and this way you only take on this dependency if you actually use it. It would likely be a bit more development effort but doable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, trimming seems sufficient to me, and wouldn't require us to ship differences in build products right?
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
⛏️ Extra newlines that trigger me.
// Try Cert store | ||
if (TryCertStore(options)) | ||
{ | ||
DoFinalValidation(); | ||
return; | ||
} | ||
|
||
|
||
// Try Assembly embedded | ||
if (TryGetEmbeddedCert(options)) | ||
{ | ||
DoFinalValidation(); | ||
return; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❓ What's the history with these? Not sure I know their use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's essentially this logic: https://github.com/bitwarden/server/blob/dae493db72b10a4cbccb0478a52bea1eb6250630/src/Core/Services/Implementations/LicensingService.cs#L47-L68 except for a few key changes.
First, I don't do any "are you self-hosted?" checks. I just check all the locations no matter what and at the end of it if I have a valid cert with a private key, you can be cloud. Also, I changed the order of checking from azure -> embedded -> cert store to the above. That's because I did away with the self hosted checks and since even our cloud builds have the embedded cert, I needed to prefer the cert store.
But we very likely could do away with the cert store. I just can't say for sure that it isn't used but it also doesn't add any dependencies.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My question was really "is this still used?" but I guess with just a few lines of code and no new dependencies it doesn't hurt to leave it.
} | ||
} | ||
|
||
// Ref: https://github.com/dotnet/aspnetcore/blob/main/src/Testing/src/Logging/XunitLoggerProvider.cs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❓ These haven't been packaged up by someone?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like yes actually, would you prefer I use this? https://github.com/martincostello/xunit-logging
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is subjective, but yeah that looks like it's popular and active so I'd prefer we outsource it vs. have our own and likely miss out on stuff, including maintenance.
New Issues
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see a few other to-dos -- merge now and solve later or what?
/// </summary> | ||
public string? CertificatePassword { get; set; } | ||
|
||
// TODO: Do we actually need to allow these to be customized? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ℹ️ Missed this ... I think no.
🎟️ Tracking
https://bitwarden.atlassian.net/browse/PM-12643
📔 Objective
Proposal of a new
ILicensingService
:This adds a
ILicensingService
to this library. It differs heavily from itsserver
counterpart. An explanation of its interface:IsCloud
, this service now becomes the arbiter of whether the running service is running in the cloud or is a self-hosted service and this property is the way to check that. It does this not through a simpleselfHosted
config key but through a capabilities check. It calls itself cloud if it has license signing abilities and it does this by ensuring it has a Bitwarden sanctioned certificate and that that certificate has a private key. It will return false when it has a Bitwarden sanctioned certificate without a private key.string CreateLicense(IEnumerable<Claim>, TimeSpan)
this is the new method for creating a license, this license can then be packaged into a file and downloaded by a user for a later upload into their self-hosted server. This method takes only two things, a list of claims, which are just a simple key value pair of data that you want packaged into the license so that it can be consumed later when the license is validated. You also give aTimeSpan
which is how long your license should be valid for.Task<IEnumerable<Claim>> VerifyLicenseAsync(string)
this is the method for consuming the license file. You give it the original license contents and, if valid, it will give you back the original claims that you gave it on the creation side. With this response you can look for and retrieve capabilities that were bestowed on that license from cloud. With proper defensive programming there should never be a license versioning problem like we've had in the past. The types of values stored in here have no rules, you can stored a value that is serialized json so that you can store complex information in the license. The license is a JWT token as apposed to the current style which is a JSON file. The contents of the license are not as simply viewable (i.e just opening the file in any IDE program) but they are still viewable with JWT tools likejwt.io
the contents should not be considered secret.We will not be able to simply switch over to this new licensing service in
server
. It will require a long migration but it is a migration we can do. All new license files that come from Bitwarden should probably switch to this format.The responsibility of building the initial list of claims and reading the claims is up to the feature team that needs to make use of the license.
Points of interest
This doesn't currently remove all reliance on the
globalSettings:selfHosted
config key. I want it to but I struggle to remove its final usage, which is being a flag to decide whether or not to read in theappsettings.SelfHosted.json
file.⏰ Reminders before review
🦮 Reviewer guidelines
:+1:
) or similar for great changes:memo:
) or ℹ️ (:information_source:
) for notes or general info:question:
) for questions:thinking:
) or 💭 (:thought_balloon:
) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion:art:
) for suggestions / improvements:x:
) or:warning:
) for more significant problems or concerns needing attention:seedling:
) or ♻️ (:recycle:
) for future improvements or indications of technical debt:pick:
) for minor or nitpick changes