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

Automating manual handler #76

Closed
seteq opened this issue Jan 20, 2016 · 47 comments
Closed

Automating manual handler #76

seteq opened this issue Jan 20, 2016 · 47 comments

Comments

@seteq
Copy link

seteq commented Jan 20, 2016

Hi,

I need to automate the certificate creation/renewal on a windows server with a special/unknown web server.

Is there a possibility to automate the manual method of Complete-ACMEChallenge?
Can the information about the file (url/content) be redirected to a powershell variable or is there a way to create that file directly?

Thank you!

@bseddon
Copy link
Contributor

bseddon commented Jan 23, 2016

Yes. Assuming you issue an update request and capture the response you can process it in PowerShell. Suppose you issue an update request like this ('dns1' was the alias used in the New-ACMEIdentifier command) :

$result = Update-ACMEIdentifier dns1 -ChallengeType http-01

Then you can process $result to extract the url and content for the file that needs to be created:

$result.Challenges | Where-Object { $_.HandlerName -eq 'manual' } | Select-Object { $_.Challenge.FileContent, $_.Challenge.FilePath }

@ebekker
Copy link
Owner

ebekker commented Jan 25, 2016

This is related to #47, part of the overall enhancement to add automated recurring renewals. As @bseddon shows, because this is all operating with POSH, even before there's official support for this, it can be scripted together to accommodate any special situation which you may have.

@bseddon
Copy link
Contributor

bseddon commented Jan 25, 2016

[Update 2017-01-02] The script is now available as in a GitHub repository

@whereisaaron is creating a variant to add support for the dns-01 method for AWS Route 53 users.

Here's a script to create/renew certificates. You can watch a video about using ACMESharp and the script on YouTube.

[Update 2016-09-21]

Added two more suggestions by @hahndorf: 1) Improved the friendly name generated to include the expiry date and that it is an LE certificate; 2) added an option that allows the deletion of old certificates to be optional. The new option is called 'keepOldCertificates' which is $false by default to retain backwards compatibility.

[Update 2016-09-20]

Made some modifications suggested by @hahndorf below. Thanks, Peter.

[Update 2016-02-06] The script has been modified to:

  1. Allow some Csr parameters to be added such as Organization, Email and Country
  2. Support alternative domain names so one certificate is able to support multiple domains
  3. Specify a web site name rather than the path to root of a web site. This is so the domains for which certificate requests are being made can validated with IIS. This is a backwards incompatible change.
  4. The IIS SSL bindings for each domain specified will be updated.

[Update 2016-02-21] Fixed a small bug line 412 where 'true' is used instead of '$true'

This script may help some people use ACMESharp. I need something like this right now so it has been necessary to put something together. It performs the steps documented on the getting started page and adds error trapping/reporting. It then goes on to remove a certificate from the certificate store if it exists, adds the new one and set a friendly name. Here is an example command to create a certificate for a single domain:

Update-Certificate-Http -alias "www-mydomain" -domain "www.mydomain.com" -websiteName "My Web Site"

This command will create a certificate for more than one domain and add the organization option to the CSR:


It should work if it's dropped into any folder and then from PowerShell run:

`. .\Update-Certificate-http.ps1`

(that's two dots separated by a space at the beginning)

I've used on my web server but it should work on a remote machine if that machine is able to access a share on the web server to access the web site root.

If you create a certificate with an alternative domain name, then the certificate will be used to update the IIS SSL bindings for each domain included.  When the bindings of the alternative domains are updated you will see an warning message:

WARNING: Binding host name 'www.xxx.com' is not equals to certificate subject name 'www.yyyy.com'. Client may not be able to connect to the site using HTTPS protocol


There's nothing wrong when you see this message.  The message is a statement of what might happen but, in this case, its not going to apply.

If you run the script with exactly the same parameters it will generate exactly the same certificate.  To generate a new certificate create a new alias.  Usually certificates will be regenerated each quarter or month so date may be a good way to make the alias unique each time the command is run.  You can use the date to create a suffix like this:

`$suffix = "-{0:D2}-{1:D2}-{2:D2}" -f [int](Get-Date).year,[int](Get-Date).month,[int](Get-Date).day`

The alias can then be something like:

"www-mydomain$suffix"


I have seen an error trying to submit a certificate.  The error has also been reported  in #89.  A fix is documented in the responses.

@ebekker
Copy link
Owner

ebekker commented Jan 27, 2016

Thanks @bseddon, I'll check it out and put it up in a contributions section for others to get at.

@bseddon
Copy link
Contributor

bseddon commented Jan 27, 2016

Would you be OK if I created a video to go with it? I don't want to get in the way of your plan or hog the limelight but equally, you've created a great tool and showing people how easy it can be to create and regenerate certificates on the Windows platform may help some get into ACMESharp.

The downside is that the script does not use the DNS option so requires access to the web site root folder. On the other hand, supporting the ability to automatically add a TXT record into any DNS server is no practical. I tried yesterday to support GoDaddy and EasySpace in addition to Route53 and gave up.

@seteq
Copy link
Author

seteq commented Jan 30, 2016

Hey @bseddon, I just tried your script and it's great. I had to modify some small parts, because that windows server is running apache and I need no import to the windows cert store. The script really simplifies the task of obtaining a certificate on windows.

But as far as I understand that the script cannot be used to automatically renew a expiring certificate. I'm still dreaming of a set-and-forget script that can just be put into the task scheduler for example. Do you think that's possible?

Thank you - both of you - for the great work!

@bseddon
Copy link
Contributor

bseddon commented Jan 30, 2016

@seteq You need to run it again with a different alias. You might call the script's function in a one line script of your own and set the alias to be 'mydomain' + somedatestring. As long as the alias is unique each time the script is run a new certificate will be requested and loaded into the certificate store replacing the one already their with the same common name (CN) field. Of course in your case you will be saving the certificate in some location referenced by httpd.conf (or a file it imports).

Maybe Eugene will comment on whether the 'reset' flag on Complete_ACMEIdentifier can be used to reuse an existing alias. I've not been motivated to play with the options because it works OK for me at the moment.

PS No credit to me. The kudos goes to @ebekker for the great implementation and especially the PowerShell support.

@ebekker
Copy link
Owner

ebekker commented Jan 30, 2016

@bseddon, I think my plans for world domination are safe, go forth and create your video, take as much limelight as you want, enjoy! When you have it ready, let me know, I'll update the contributions page to include the link to it as well, thanks.

It's true the DNS option is tricky because there is no standard for DNS management, so individual hosting providers will need their own providers in ACMESharp to be supported. Having said that, if you want to support a DNS hosting service natively, it needs to expose an API to support true automation, and the two that you mention don't seem to offer that unless you use their reseller services. I think that is by design based on their sales model.

However, there are many other hosting providers that do offer APIs and I plan to either add them myself or support others to contribute (Azure, Google DNS, EasyDNS just to name a few).

I started with AWS R53 just because that's a service that I have plenty of past experience with and just needed something to prove out the model with. I think the DNS challenge type is quite important for ACME and LE in general because it's the only way for an org to secure an internal (non-public) HTTPS interface.

And of course, even if there is no automated support for it, there is always the manual provider which just gives you the RR records that need to be created.

@bseddon
Copy link
Contributor

bseddon commented Feb 2, 2016

@ebekker Yesterday I added a link to my comment further up this thread which points to a video about using your PowerShell commands to automate the generation of LE certificates. However, I forgot that you don't see my edits so this is a note to fix that.

@AnderssonPeter
Copy link

@bseddon Any chanse you could add support for -AlternativeIdentifierRefs?

@bseddon
Copy link
Contributor

bseddon commented Feb 4, 2016

@Petoj87 Good point. I'll take a look at that. Certificate details as well (Country, StateOrProvince, Description, etc.)

@bseddon
Copy link
Contributor

bseddon commented Feb 6, 2016

@Petoj87 I've added support for alternative identifiers so one certificate is able to support multiple domains. There's a Let's Encrypt imposed limit of 5 certificate requests each 7 days at least for the public beta. Because of this limitation a certificate can only include 5 requests for the same domain.

@scott10
Copy link

scott10 commented Feb 27, 2016

I started to write something similar to this, then bam! not only does it exist, its far more in depth than I intended. Thanks.

@reidca
Copy link

reidca commented Jun 21, 2016

I had the same question about how we can automate the DNS challenge. I was hoping the name of the resource record to create and the value would be part of the PowerShell object returned but I can't see that.

It seems this information is only returned to the host which I cannot understand. Why return an object but not include this information in it?

I have tried the following code modified from the example above:

$result = Update-ACMEIdentifier dns1 -ChallengeType dns-01
$result.Challenges | Where-Object { $_.HandlerName -eq 'manual' } | Select-Object { $_.Challenge.FileContent, $_.Challenge.FilePath }

But both parts come back as null. I assume this is because these properties are only valid in the http challenge.

Simply put, how do I get the DNS resource record name and the value into an object in PowerShell?

Thanks

@bseddon bseddon mentioned this issue Jul 18, 2016
@ghost
Copy link

ghost commented Jul 19, 2016

@bseddon

I just started using your automated script, and I typed the following
Update-Certificate-Http -alias "dns-07192016" -domain "www.mydomain.com" -websiteName "domain.com"

and it says
One or more of the challenges of the identifier for alias 'dns-07192016' is invalid

@bseddon
Copy link
Contributor

bseddon commented Jul 19, 2016

Yes, it will unless you own 'www.mydomain.com' on an IIS site definition you have named 'domain.com'

These parameters need to be your web site and the name of the web site definition in your instance of IIS.

@ghost
Copy link

ghost commented Jul 19, 2016

I'm little confused, ok so my IIS under sites, i have a site called ninjablume.com and i added https://ninjablume.com to bind it, which it tells me too.

Question
There's no need to edit anything on the files when uploaded right. assumed that's the case.

Update-Certificate-Http -alias "dns-07202016" -domain "ninjablume.com" -websiteName "ninjablume.com"

I'm still getting that same message.

@bseddon
Copy link
Contributor

bseddon commented Jul 19, 2016

Your new command looks good except you can't reuse the alias. In your last email you said the alias 'dns-07192016' was invalid. If you try to reuse it you are telling the script to use an invalid alias which will always fail. Just make up a new name.

@ghost
Copy link

ghost commented Jul 19, 2016

i actually did that, but i changed it to like dns10-2016 and it still failed I end up creating fake test.com. and it still does the same thing.

test.com bad example

@bseddon
Copy link
Contributor

bseddon commented Jul 19, 2016

You need to do more than claim it has failed. The script executes 8 or more ACMESharp commands and the failure could be anywhere. There will be a lot of output as the script runs. Why not post it here otherwise I'm just guessing.

Have you used the -CheckParameters option so the script will check you are providing valid parameters?

@bseddon
Copy link
Contributor

bseddon commented Jul 19, 2016

I don't understand your question. I don't understand what is asking you to bind HTTPS to the domain? Can you cut-and-paste the message or include a screenshot?

@ghost
Copy link

ghost commented Jul 19, 2016

Check that the domains for which certificates are to be generated have an https binding in the web site

$invalidDomains = $domains.GetEnumerator() | 
    Where-Object { $dom = $_.Value;
        ! (Get-WebBinding -HostHeader $_.Value -Protocol https) -or 
        ! ( $webSite.bindings.Collection | Where-Object { $_.protocol -eq 'https' -and $_.bindingInformation -like "_:$dom_" } ) 
    }
if ( $invalidDomains.Count -gt 0 )
{
    "The following domain(s) do not have an https binding in the web site '$WebSiteName': {0}" -f  (@( $invalidDomains | ForEach-Object { $_.Value } ) -join ",")
    "Please add binding(s) in IIS before using these domain(s)"
    return;
}

@bseddon
Copy link
Contributor

bseddon commented Jul 19, 2016

You can't reuse aliases. An alias represents information held on the Let's Encrypt server. A copy of this information is held in the vault. I think @ebekker has created a topic here to review this constraint so that we can remove the information and the alias from the vault. But until then its necessary to create a new identifier (alias) for each new certificate.

@bseddon
Copy link
Contributor

bseddon commented Jul 19, 2016

Good point about the binding check.

@ghost
Copy link

ghost commented Jul 19, 2016

Let me rephrase this so we don't get confused.

First, the automation works, which is great, thanks. so i came conclusion that it is not really necessary to create a new -alias when you renew the certificate, So what i did was i found a certifcate earlier and found out the -alias and i just update that one instead using the same format

Update-Certificate-Http -alias "dns3-07152016" -domain "ninjablume.com" -websiteName "ninjablume.com"

it renew the certification.

@bseddon
Copy link
Contributor

bseddon commented Jul 19, 2016

No, it will not renew the certificate. When ACMESharp first generates a certificate it is stored in the vault. When you re-run the script with the same alias, it finds the alias in the vault then finds the previously generated certificate and re-applies it to IIS.

Let's Encrypt certificates last just 90 days. So you will need to re-run the script with a different alias so a new certificate is generated and applied to your site.

@ghost
Copy link

ghost commented Jul 19, 2016

really because i just did that and it renewed, see the following.
cert
eagle4000.txt

@bseddon
Copy link
Contributor

bseddon commented Jul 19, 2016

I think you have used the alias dns1-07162016 (which is shown in the .txt file) to complete the challenge but you had not generated a certificate for this alias. You can see in the output that the alias was found but certificate had not yet been generated for that alias.

If you re-run the command now using the same alias (dns1-07162016) I think you will see the output is different. Instead of text stating that it is creating a new certificate the output will include a line something like:

"A certificate for 'certdns1-07162016' already exists with id xxx"

@ghost
Copy link

ghost commented Jul 19, 2016

is so weird ok so i did exactly the same as you referring to the alias needs to be change right so here's my output. but why would it be invalid.
eagle4000-new with new alias.txt

@bseddon
Copy link
Contributor

bseddon commented Jul 19, 2016

Issue the command:

Get-ACMEIdentifier eagl4000-07192016

The output will show a 'uri' value. This is a link to a page on the Let's Encrypt servers which contains challenge status and other information. It will contain information you may be able to use to find out why this identifier could not be validated.

@ghost
Copy link

ghost commented Jul 19, 2016

would CloufFlare be causing this, because I'm using cloudflare..
{ "type": "http-01", "status": "invalid", "error": { "type": "urn:acme:error:unauthorized", "detail": "Invalid response from http://www.eagle4000.com/.well-known/acme-challenge/5M-gTpufumFt8_IJC54Mm2EDGcwzE8Pj5Qy7QNjse3c [104.31.88.180]: 404", "status": 403 }, "uri": "https://acme-v01.api.letsencrypt.org/acme/challenge/_0ScyHs_BtrFFtgQBEMrj4ZZTKcA5B0bWcCasJ6XwLw/185254475", "token": "5M-gTpufumFt8_IJC54Mm2EDGcwzE8Pj5Qy7QNjse3c", "keyAuthorization": "5M-gTpufumFt8_IJC54Mm2EDGcwzE8Pj5Qy7QNjse3c.CUdS6skCWU5V_9idhVXrwZ94TfqQ1C0dzV96DssjaU0", "validationRecord": [ { "url": "http://www.eagle4000.com/.well-known/acme-challenge/5M-gTpufumFt8_IJC54Mm2EDGcwzE8Pj5Qy7QNjse3c", "hostname": "www.eagle4000.com", "port": "80", "addressesResolved": [ "104.31.88.180", "104.31.89.180" ], "addressUsed": "104.31.88.180" } ] }

@bseddon
Copy link
Contributor

bseddon commented Jul 19, 2016

Maybe. Based on the information in the file 'eagle4000-new with new alias.txt' I would expect to see the challenge token in a file referenced by this url but there is nothing there:

http://www.eagle4000.com/.well-known/acme-challenge/5M-gTpufumFt8_IJC54Mm2EDGcwzE8Pj5Qy7QNjse3c

@ghost
Copy link

ghost commented Jul 19, 2016

Let me do some testing then, I'll let you know so if people actually using cloudlfare then its an issue. I know the codes and the automation works because I tested them manually. all you did was just added scripts and automate everything so im going to assume it could be a cloudflare issue that doesn't like it.

I totally appreciate your patience with me with all these nonsense stuff but its a learning process..

@ghost
Copy link

ghost commented Jul 20, 2016

if i don't have a certificate install for a specific domain, the automation will keep asking me to add https binding in IIS before using these domains. is there a way for me to bypass this.

@bseddon
Copy link
Contributor

bseddon commented Jul 20, 2016

Just use the certificate of another site to create the initial binding. It doesn't matter that the certificate is not valid for the site because it will be replaced as soon as the script is run.

@ghost
Copy link

ghost commented Jul 20, 2016

@bseddon

Thanks, it still doesn't work. after it try to create a new certification all the sudden it gives me permission denied. I think i have no luck with this automated script. thanks anyways i'll just do it manually for now.

@ghost
Copy link

ghost commented Jul 20, 2016

i finally got it to work @bseddon there was a conflict where i was using a built in control panel that supports Let's Encrypt, but since if i want to use this automation. then i cannot use the builtin control panel version of ACMESharp. ;)

@hahndorf
Copy link

I like to propose two changes to Update-Certificate-http.ps1, but it seems that file is not part or any repository?
On line 577 it says:

(Get-Item "Cert:\LocalMachine\WebHosting\$thumbprint").FriendlyName = $domain

even though I specified the -certPath parameter to \LocalMachine\My so it should say:

(Get-Item "Cert:$certPath\$thumbprint").FriendlyName = $domain

On line 215:

$websiteFolder = $webSite.physicalPath

My physicalPath looks like %WebSitesDrive%\sitename\files, so it is using an environment variable, just in case I have to move all files to a different drive. To make this work add a line below:

$websiteFolder = [System.Environment]::ExpandEnvironmentVariables($websiteFolder)

@bseddon
Copy link
Contributor

bseddon commented Sep 20, 2016

HI Peter

Thanks for the suggestions. I've updated the zip file in the comment above to include your suggestions. If you get a chance to look at the update to make sure I've applied them correctly it will be great.

Regards

Bill Seddon

@hahndorf
Copy link

Hey Bill,

yes, that worked fine, I just started using LetsEncrypt and manage a server with just 15 certificates, but it seems to work fine. I made two more changes to your script:

I don't delete old certificates straight away, I like to keep them as a backup, just in case something is wrong with the new one. A different clean-up script deletes them later.

Rather than just using the hostname for the friendly name, I added the expiry date of the certificate, this way I can see in IIS when they expire (around line 606).

$expires = ((Get-Item "Cert:$certpath\$thumbprint").notAfter).ToString("yyyy-MM-dd")
(Get-Item "Cert:$certpath\$thumbprint").FriendlyName = "$domain $expires LE"

also adding an "LE" for lets encrypt, because I still use other CAs.

These changes should be configurable via parameter.

Not essential, but that's how I like it, thanks for your work.

@bseddon
Copy link
Contributor

bseddon commented Sep 21, 2016

Thanks again for your suggestions. I've implemented both and updated the file. Generating a more informative friendly name seems like a good idea as does making the viewer aware of the source of the certificate. I take you point that the deletion of old certificates should be optional.

I learned something new along the way: the existence of the 'notAfter' property.

Regards

Bill

@hahndorf
Copy link

Thanks Bill,

I wrote a short blog post about my experience with ACMESharp. I now use ACMESharp, your script and my own meta script to automatically update all Let's Encrypt certificates on my server that are about to expire. It needs more testing, but I now have a fully automated solution to update/renew all certificates.

@bseddon
Copy link
Contributor

bseddon commented Sep 21, 2016

Nice, thanks for letting me know. I have another potential topic for you to include. You probably don't see it because you only used https before you started using ACMESharp.

Imagine you are new to SSL and choosing to use ACMESharp for the first time so there are only http sites. In this case the script will fail in the pre-check complaining there is no existing https binding. It's complaining because it's not going to add an https binding only update/replace the certificate associated with an exising https binding.

The solution is to add an https binding using the IIS manager and associate any certificate with that binding. It doesn't matter that the certificate is not valid for the domain it just needs to fool IIS into adding the binding. As soon as the script is run the invalid certificate will be replaced and life is good once again.

Bill

@ebekker
Copy link
Owner

ebekker commented Sep 22, 2016

Thanks for the write up, I've added your blog post to the contributions page.

@whereisaaron
Copy link

I have completed the update to @bseddon's script for ACMESharp to also support other types of automated ACME challenges, not just http-01. I have been using it with dns-01 challenges with AWS Route 53. This new version is in my repo, though @bseddon has created a repo for his script now too, and he may integrate my changes there.

You just need an AWS IAM key/secret and the AWS Route 53 'Hosted Zone Id' for the public DNS domain. Your IIS website can be private, or password-protected, or HTTPS only and this method will still works.

Update-Certificate -alias "www1" -domain "www.example.com" -websiteName "My Website" -ChallengeType "dns-01" -ChallengeHandler "awsRoute53" -ChallengeParameters @{HostedZoneId="ZX1234567890";AwsProfileName="default"}

It even works to issue a certificate if you don't have IIS running at all, e.g. for another web server or service.

Update-Certificate -alias "api1" -domain "api.example.com" -notIIS -ChallengeType "dns-01" -ChallengeHandler "awsRoute53" -ChallengeParameters @{HostedZoneId="$vpcZoneId";AwsProfileName="default"}

@ebekker
Copy link
Owner

ebekker commented Aug 8, 2017

Manual Challenge Handler has been updated with a new OutputJson parameter that spits out the handling details as JSON blob for easier parsing. See #270 for details.

@ebekker ebekker closed this as completed Aug 8, 2017
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

8 participants