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

Investigate "stateless" approach #46

Closed
glatzert opened this issue Nov 25, 2019 · 35 comments
Closed

Investigate "stateless" approach #46

glatzert opened this issue Nov 25, 2019 · 35 comments

Comments

@glatzert
Copy link
Collaborator

First, I don't mean to pile on because I think the work you've done here is terrific Tom. Thank you for making such a valuable contribution to the developer community. And please let me know if my next comments would be more appropriate elsewhere.

I have seen the same issue reported by Slamich and I independently arrived at the same solution (ie. "openssl.exe"). I am fine with leaving it at that.

"The fact is that there were no such problems with the previous ASMESharp module."

This is my real concern.

I am sure there are many others who migrated to "ACME-PS" from "ASMESharp" (who are also very grateful for your efforts Tom). With that migration in mind I have been wondering about something else.

One nice feature of "ASMESharp" was its "stateless" implementation (ie. it saved 100% of its own state to disk). That approach allowed its various commands to be run independently in multiple (ie. separate) Powershell sessions (one after another) without concern for losing state.

"ACME-PS" does not allow this. While "ACME-PS" saves some of its state to disk, the balance requires the use of temporary in-memory PS variables. That means users must complete the entire process in a single Powershell session.

Can you please explain that design choice Tom? Is there an easy way to arrive at a similar "stateless" approach (ie. similar to how "ASMESharp" manages 100% of its own state itself) ?

Originally posted by @GeorgeSchiro in #45 (comment)

@glatzert
Copy link
Collaborator Author

I see the problem, that this is kind of an successor to ACMESharp, but the focus is a little bit different indeed. The ACMESharp module has not been stateless, but it hid away the state from you, by managing it in the background in a fully automatic fashion. IIRC it also was able to manage IIS-Sites automatically and such.
While such an approach hides some of the complexity, it'll make it difficult to reach into the process and get intermediate results. The approach here is more to provide a protocol handler, and let the specifics to the user / administrator / script implementer, e.g. I'll provide you with everything neccessary to fulfill the AuthZ, but you know your environment the best and just need some lines of PS to put the challange-files where they need to be or to modify your DNS.

About the scope of the module:
The hard part of ACME is to have something speaking the protocol and correctly answering the authorizations. The module will help with the protocol and provide the means to answer the authorization. It tries not to assume your environment and thus, it's made to be used in your scripts - not to replace them.

If there are possibilities to make the module more beginner friendly and/or usefull, I'm all ears,
Also I understand, that migrating from ACMESharp might be steep. Perhaps I - or we? - can provide guidance how to migrate from common scenarios.

Oh and btw:

@GeorgeSchiro
Copy link

GeorgeSchiro commented Nov 25, 2019 via email

@glatzert glatzert changed the title Investigate stateless approach Investigate "stateless" approach Nov 25, 2019
@glatzert
Copy link
Collaborator Author

Essentially every command until certificate key generation and exporting the completed certificate will use the State-Object and will be able to make use of it.
That's probably something which could be added, so the CertKey will be stored along the order - probably removing a pain point for "multi-session"-users.
Also an automatic update of the order from HDD might be feasable - same for other objects like account-key (which is most likely not needed to be updated frequently).
The state itself could be made automatically available to running cmdlets by leveraging the $PSDefaultParameterValue-Hashset (at least as an opt-in).
On top of that, there could be a single-line-initialization for the state, using some default path as well as a default-path for the state.

What do you think are other actionable items?

@GeorgeSchiro
Copy link

GeorgeSchiro commented Nov 25, 2019 via email

@glatzert
Copy link
Collaborator Author

glatzert commented Nov 26, 2019

So - to allow this the following has to happen:

  • Include CertificateKey in State or Order, since it currently is not saved alongside those objects
  • Allow Complete-Order to call New-CertificateKey, if neccessary (probably opt-in)
  • Change implementation of State to load certain objects ad-hoc (e.g. next-nonce) instead of one-time
  • Add -StatePath Parameter to all cmdlets supporting -State TypeConverter string -> AcmeState

I don't see any donwsides to this approach currently.
Changing the state-object needs caution, due to probability of breaking changes, but else ...

@GeorgeSchiro
Copy link

I know that's a lot to think about (let alone do). To obviate the need for always having to use a single session, an alternate syntax for every command would be the ideal. But perhaps there is an intermediate step that would serve the same purpose.

Consider defining 4 new commands to write state to disk:

Set-ACMEAuthorization $authZ     -Path "path to ACME working folder";
Set-ACMEChallenge     $challenge -Path "path to ACME working folder";
Set-ACMEOrder         $order     -Path "path to ACME working folder";
Set-ACMEState         $state     -Path "path to ACME working folder";

Plus 4 corresponding commands to read state back from disk:

$authZ     = Get-ACMEAuthorization -Path "path to ACME working folder";
$challenge = Get-ACMEChallenge     -Path "path to ACME working folder";
$order     = Get-ACMEOrder         -Path "path to ACME working folder";
$state     = Get-ACMEState         -Path "path to ACME working folder";

Does that make more sense Thomas? Would that cover all multi-session scenarios?

@glatzert
Copy link
Collaborator Author

glatzert commented Dec 1, 2019

Hi George,

I'd always use -StatePath, to make the purpose clear - but that's only a side-note.

PowerShell allows to define ParameterSets, which allows to disambiguate between different modes of CmdLets, thus I'll probably define "overloads" of the given commands which accept will accept either State or StatePath. Besides that there needs to be an Update-ACMEWhatever to allow for reading it from the LE-Servers again.
So essentially, the Get-ACME Commands will be extended, to allow those scenarios and Update-ACME will be provided, where Re-Reading is necessary (as already done with the Order).

To make all that "multi-session" enabled, the State-Object itself needs to read everything on demand instead of reading on creation and thus will need some changes (especially regarding the order objects). This is especially true for concurrent multi-sessions. But I don't like to have surprises, if users (for whatever reason) switch between multiple sessions more or less simultaneously.

So the first thing I'll be doing is making the State not doing everything in Memory, but directly interacting with the underlying storage - especially for loading.

@glatzert
Copy link
Collaborator Author

glatzert commented Dec 2, 2019

I started workin on it - but it'll probably take some time.
I began with splitting the AcmeState Object into multiple ones, all implementing the same "interface" making the code simpler for in-memory state vs persisted state. The latter one will always load from disk and not store anything in memory.

If that's finished, I'll look into storing the certificate-key alongside the order.
When that's done, it'll be fairly simple to enhance all cmdlets to allow getting a statepath instead of a state.

@GeorgeSchiro
Copy link

GeorgeSchiro commented Dec 2, 2019 via email

@GeorgeSchiro
Copy link

Happy New Year Thomas!

I've been wondering if there has been anything new on this topic. I noticed a new branch named "deep-state" (cute).

Any bearing?

@glatzert
Copy link
Collaborator Author

glatzert commented Jan 9, 2020

Essentially its done in the aforementioned branch - except one thing:
I'd like to use the -State Parameter, that exists everywhere and define an automatic conversion FROM String. Currently I could not get that running and I have no clue what the problem is.

I have yet to reach out to StackOverflow and/or the PowerShell-Gurus, I know to get that figured out.
Or I have to make the Parameter "dynamic" and accept string as well as AcmeState, which would then allow to convert manually - but I'd like not to.

Happy new year.

@GeorgeSchiro
Copy link

Fantastic!

Is "deep-state" at a point I can test? Or do you need to resolve the "-State Parameter" issue first?

@glatzert
Copy link
Collaborator Author

The last state I commited, should be usable, if you use Get-AcmeState as before.
I already issued some test certificates with that branch, so if anything goes wrong, it'll probably be a minor problem.

@glatzert
Copy link
Collaborator Author

Ah sorry - the automatic certificate key management is missing - I forgot about that.
Nevertheless - the "heavy lifiting" (still except the TypeConversion) is done - the rest should be easily doable.

@GeorgeSchiro
Copy link

Ok, I will check in later. Thanks Thomas!

@glatzert
Copy link
Collaborator Author

The State parameter will now accept strings.
Next stop: CertificateKeys

@glatzert
Copy link
Collaborator Author

I'll push the current changes to the gallery and close this issue after that.
CertificateKey handling has gotten a new issue number :)

@GeorgeSchiro
Copy link

It looks like "1.1.3-beta" in the gallery is what I should be testing. Please correct me if I mistaken in this Thomas.

This is what I did with version "1.1.0":

$state = New-ACMEState -Path "C:\Users\admin\Desktop\tmp\AcmeState"
Get-ACMEServiceDirectory $state -ServiceName "LetsEncrypt" -PassThru

Perhaps I am missing something basic with usage. Here's what I see with a preliminary test:

New-ACMEState -Path "C:\Users\admin\Desktop\tmp\AcmeState"
AcmeDiskPersistedState

That looks fine (I think). Here's where I get an exception:

Get-ACMEServiceDirectory "C:\Users\admin\Desktop\tmp\AcmeState" -ServiceName "LetsEncrypt" -PassThru

Get-ACMEServiceDirectory : Cannot process argument transformation on parameter 'State'. Cannot convert the "C:\Users\admin\Desktop\tmp\AcmeState" value of type "System.String" to type "AcmeState".
At line:1 char:26
+ ... t-ACMEServiceDirectory "C:\Users\admin\Desktop\tmp\AcmeState" -Servic ...
+                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidData: (:) [Get-ACMEServiceDirectory], ParameterBindingArgumentTransformationException
    + FullyQualifiedErrorId : ParameterArgumentTransformationError,Get-ACMEServiceDirectory

My guess is a problem with the new "StringToAcmeStateConverter" class or I just don't yet understand how to use it.

@glatzert
Copy link
Collaborator Author

glatzert commented Jan 20, 2020 via email

@GeorgeSchiro
Copy link

$PSVersionTable.PSVersion

Major  Minor  Build  Revision
-----  -----  -----  --------
5      1      14409  1005

I manually downloaded, extracted and copied the "1.1.03-beta" files myself. Here's the file list:

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----        1/18/2020  10:01 AM           2369 ACME-PS.psd1
-a----        1/18/2020  10:02 AM          92412 ACME-PS.psm1
-a----        1/15/2019   7:44 PM            376 Prerequisites.ps1
-a----        1/19/2020   5:10 PM          21600 PSGetModuleInfo.xml
-a----        1/15/2019   7:44 PM            603 TypeDefinitions.ps1
-a----        1/17/2020   9:44 PM            170 Types.ps1xml

That shouldn't make any difference, right?

@glatzert
Copy link
Collaborator Author

No it should not - at least not to my knowledge.
The presence of the Types.ps1xml is a good indicator for the correct version.

My build version differs ... But I'll take a look into the module code again, before jumping into that rabbid hole ...

@glatzert
Copy link
Collaborator Author

glatzert commented Jan 20, 2020

I ran everything on a Azure Machine, and it works fine. Are you positive you loaded (or imported) the new module version? You can check that by running Get-Module ACME-PS

@GeorgeSchiro
Copy link

Get-Module ACME-PS

ModuleType Version    Name                                ExportedCommands
---------- -------    ----                                ----------------
Script     1.1.3      ACME-PS                             {Complete-ACMEChallenge, Complete-ACMEOrder, Export-ACMEAccountKey, Export-ACMECertificate...}

@GeorgeSchiro
Copy link

Sorry Thomas, here's what I get when I try the typical install (1.1.3-beta instructions):

Install-Module -Name ACME-PS -AllowPrerelease

Install-Module : A parameter cannot be found that matches parameter name 'AllowPrerelease'.
At line:1 char:30
+ Install-Module -Name ACME-PS -AllowPrerelease
+                              ~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Install-Module], ParameterBindingException
    + FullyQualifiedErrorId : NamedParameterNotFound,Install-Module

@glatzert
Copy link
Collaborator Author

If it does not know -AllowPrerelease, you need to Install and then Update some Modules around PowershellGet.
Make sure to restart the Shell after installing, cause it will keep the old version until that:
https://www.thomasmaurer.ch/2019/02/update-powershellget-and-packagemanagement/

@glatzert
Copy link
Collaborator Author

This might also be true for the ACME-PS Module - If you loaded an older one and the new one, IPMO might have done nothing, since there was already a version of the module

@glatzert
Copy link
Collaborator Author

BTW Get-Module will report 1.1.3, since it can't handle Prereleases.

@GeorgeSchiro
Copy link

I did a package & get update:

Install-Module -Name PackageManagement -Repository PSGallery -Force
Install-Module -Name PowerShellGet -Repository PSGallery -Force

Using the same manually downloaded, extracted and copied "1.1.03-beta" files I tested yesterday, I see this:

New-ACMEState -Path "C:\Users\admin\Desktop\tmp\AcmeState"

AcmeDiskPersistedState

Get-ACMEServiceDirectory "C:\Users\admin\Desktop\tmp\AcmeState" -ServiceName "LetsEncrypt" -PassThru

ResourceUrl : https://acme-v02.api.letsencrypt.org/directory
NewAccount  : https://acme-v02.api.letsencrypt.org/acme/new-acct
NewAuthz    :
NewNonce    : https://acme-v02.api.letsencrypt.org/acme/new-nonce
NewOrder    : https://acme-v02.api.letsencrypt.org/acme/new-order
KeyChange   : https://acme-v02.api.letsencrypt.org/acme/key-change
RevokeCert  : https://acme-v02.api.letsencrypt.org/acme/revoke-cert
Meta        : AcmeDirectoryMeta

It's working now. Thank you for your patience Thomas. I will proceed with full testing soon.

@GeorgeSchiro
Copy link

I know we can reconstitute an authorization (BTW, you have "Authroization" as one of the synopsis bullets in "README.md") from an existing order like this:

$authZ = Get-ACMEAuthorization -State "C:\Users\admin\Desktop\tmp\AcmeState" -Order $order

It's not clear to me how to reconstitute an order. What we've done previously is this (with $state in place of the path string):

$order = New-ACMEOrder "C:\Users\admin\Desktop\tmp\AcmeState" -Identifiers $identifiers

But this requires "$order" to kept in memory for later use.

I tried this:

$order = Get-ACMEOrder -State "C:\Users\admin\Desktop\tmp\AcmeState"

But this fails on a missing "Url" argument (a value we would not know).

So the question remains, how do we reconstitute an order knowing just the state path?

@GeorgeSchiro
Copy link

After reviewing a successful SAN submission with several names (done via 1.1.0 code), I noticed everything is handled in what appears to be a single order (eg. one "Order-GLC_pFUkUY6z8QXOYx7acYGmX_I0lHycNcek68aprcg.xml" file with all SANs listed).

This means we only ever have one order to be concerned with.

Thomas, is there a way (currently) to "get the first order" or "get the only order out there"?

@glatzert
Copy link
Collaborator Author

glatzert commented Jan 23, 2020 via email

@GeorgeSchiro
Copy link

"1.1.0 code means, I did not break anything in the new beta?"

We haven't completed our testing, so can't say yet what may or may not be broken in the beta.

"1.1.0 code" meant I looked at "AcmeState" for a process we have still running the 1.1.0 code.

@glatzert
Copy link
Collaborator Author

The 1.1.0 code should also support the $state.FindOrder(). It takes an array of DNS names to find the best matching order in the state

@glatzert
Copy link
Collaborator Author

1.1.3-beta2 now comes with Find-Order

@glatzert glatzert mentioned this issue Jan 25, 2020
@glatzert
Copy link
Collaborator Author

This should all work in 1.1.3-beta3, so I'm closing the issue.

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

No branches or pull requests

2 participants