rmbolger / Posh-ACME

PowerShell module and ACME client to create certificates from Let's Encrypt (or other ACME CA)
https://poshac.me/docs/latest/
MIT License
749 stars 184 forks source link

Cloudflare Plug In fails to convert String to SecureString #530

Closed fthobe closed 6 months ago

fthobe commented 8 months ago

Hey,

I tied to work my way through a DC installation for the first time in decades and beeing a little cert obsessed I googled and found your marvellous article, as luck wanted it, cloudflare was supported by Posh Acme so I figured why not try.

Env: Win 22 Standard, fresh AD, prerequisites given

# Cloud Flare requires a simple API token
$pArgs = @{CFToken='thatisactuallysecret'}

# The ActiveDirectory PowerShell module is installed by default on DCs
$dc = Get-ADDomainController $env:COMPUTERNAME
$certNames = @($dc.HostName, $dc.Domain)

# This is optional, but usually a good idea.
$notifyEmail = 'dev@mydomain.com'

$certParams = @{
    Domain = $certNames
    DnsPlugin = 'Cloudflare'
    PluginArgs = $pArgs
    AcceptTOS = $true
    Install = $true
    Contact = $notifyEmail  # optional
    Verbose = $true         # optional
}

New-PACertificate @certParams

And got following error:

PS C:\Windows\system32> certificatePull
VERBOSE: Updating directory info from https://acme-v02.api.letsencrypt.org/directory
VERBOSE: Using ACME Server https://acme-v02.api.letsencrypt.org/directory
VERBOSE: Using account 1506562526
VERBOSE: Order name not specified, using 'mi-dc-pr-1.mydomain.com'
VERBOSE: Using existing order 'mi-dc-pr-1.mydomain.com' with status pending
VERBOSE: Setting Plugin to Cloudflare
VERBOSE: Updating plugin args for plugin(s) Cloudflare
VERBOSE: Saving order changes
WARNING: Fewer Plugin values than names in the order. Using Cloudflare for the rest.
VERBOSE: Publishing challenge for Domain mi-dc-pr-1.mydomain.com with Token R9eP-UD0Bcuk1zw5IUilCHlLwhYChDbE3bPmpAfrelc using Plugin Cloudflare and DnsAlias
''.
Submit-ChallengeValidation : Cannot process argument transformation on parameter 'CFToken'. Cannot convert the "thatisactuallysecret" value
of type "System.String" to type "System.Security.SecureString".
At C:\Program Files\WindowsPowerShell\Modules\Posh-ACME\4.20.0\Public\New-PACertificate.ps1:253 char:9
+         Submit-ChallengeValidation
+         ~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidData: (:) [Submit-ChallengeValidation], ParameterBindingArgumentTransformationException
    + FullyQualifiedErrorId : ParameterArgumentTransformationError,Submit-ChallengeValidations

The weird stuff is comparing Digital Ocean success and my Cloudflare copy cat attempt (thanks :) I couldn't spot any difference or mistake on my site.

Also using curl -X get on the api key succeded, so I am kind of out of Ideas.

-f

rmbolger commented 8 months ago

Hey @fthobe, thanks for reaching you. You're SO close. There's only one tiny mistake translating the Digital Ocean instructions to Cloudflare that's tripping you up.

The DOToken parameter for the Digital Ocean plugin is a standard PowerShell string value as the blog post shows. However, the CFToken parameter for Cloudflare actually needs to be a SecureString value such as the type returned by Read-Host -AsSecureString. You can check the various parameter set details for a given plugin with the by running Get-PAPlugin Cloudflare -Params.

If you're running the script interactively, the Read-Host -AsSecureString can ensure you don't leave a copy of your API token in your command history.

# this will prompt you to enter the value interactively, but keep it out of your history
$token = Read-Host -AsSecureString
$pArgs = @{CFToken=$token}

Alternatively, if you're not running this interactively or otherwise getting the plaintext token from elsewhere and need to convert it into a SecureString value, this would work instead.

$token = ConvertTo-SecureString 'thatisactuallysecret' -AsPlainText -Force
$pArgs = @{CFToken=$token}

You may also notice there is a parameter set that allows for a CFTokenInsecure parameter which is a normal string instead of the default CFToken param. You could use this instead, but the insecure parameter sets are technically deprecated pending removal in the next major version (5.x) of the module. So it's more future proof to stick with the SecureString version.

fthobe commented 8 months ago

This is amazing :) thank you for your reply. For the record I attached the code below.

I am left with two questions puzzling me!

# Cloud Flare requires a simple API token, but we need to secure the string to keep it safe
$token = ConvertTo-SecureString 'thatisactuallysecret' -AsPlainText -Force
$pArgs = @{CFToken=$token}

# The ActiveDirectory PowerShell module is installed by default on DCs
$dc = Get-ADDomainController $env:COMPUTERNAME
$certNames = @($dc.HostName, $dc.Domain)

# This is optional, but usually a good idea.
$notifyEmail = 'dev@gmservice.app'

$certParams = @{
    Domain = $certNames
    DnsPlugin = 'Cloudflare'
    PluginArgs = $pArgs
    AcceptTOS = $true
    Install = $true
    Contact = $notifyEmail  # optional
    Verbose = $true         # optional
}

New-PACertificate @certParams
rmbolger commented 8 months ago

Is the wildcard necessary? Keep in mind, wildcards are only valid for a single level. So *.example.com will be valid for bar.example.com but not foo.bar.example.com. In any case, you'd add a third name to the $certNames variable. Something like:

$certNames = @($dc.HostName, $dc.Domain, "*.$($dc.Domain)")

But I think that wildcard would overlap with the $dc.HostName value and some CAs don't allow that (like Let's Encrypt). So you would have to remove the explicit hostname. But without the explicit hostname, I'm not sure if the AD engine will pick up the cert properly for LDAPS.

For RDP, I'd recommend using the Set-RDSHCertificate function from my Posh-ACME.Deploy module.

fthobe commented 8 months ago

$certNames = @($dc.HostName, $dc.Domain, "*.$($dc.Domain)") Your comment regarding acceptance is good to know. So your advice is to always to run a script per domain per FQDN certs or did you discover a workaround?

For RDP, I'd recommend using the Set-RDSHCertificate function from my [Posh-ACME.Deploy](https://github.com/rmbolger/Posh-ACME.Deploy) module. I will give that a try later tonight.

BTW: I could fire up an Exchange 2022 just to test ACME.Deploy as the email address policy would come in handy.

rmbolger commented 8 months ago

So your advice is to always to run a script per domain per FQDN certs or did you discover a workaround?

I don't know of a workaround for AD domain controller cert picking thing. But there are far more knowledgeable folks than I who might.

The advice is more that you should shift your mindset away from trying to do everything on every server with a single all-encompassing cert. That sort of thinking only made sense when getting a certs was expensive and time consuming. But they're free now and once you've gotten a handle on your renewal automation strategy, they require very little maintenance. From a security standpoint, it's also safer not to be transferring/copying a cert's private key all over your network.

fthobe commented 8 months ago

Hey, so I read up some stuff regarding posh-acme.deploy and I figured out some questions for you.

1) '$dc = Get-ADDomainController $env:COMPUTERNAME' renders this script only valid on DCs, right? What happens if I try to run this on a non DC. I actually can't find any reference of DC in the lines below. Couldn't I just use HostName to get the hostname as FQDN and 2) I saw that Posh-ACME.deploy supports a variety of services, what would be your approach to search for the existence of a service and verify if a part of a script needs to be run for services present before running it? 3) Is the regular remote access using the same certificate?

I feel like I really owe you a coffee.

# Cloud Flare requires a simple API token, but we need to secure the string to keep it safe
$token = ConvertTo-SecureString 'secret' -AsPlainText -Force
$pArgs = @{CFToken=$token}

# The default certificate password is "poshacme", but we prefer some extra security. At this point you could also just use the CF token. 
$CertPass = 'anothersecret'

# The ActiveDirectory PowerShell module is installed by default on DCs.
$dc = Get-ADDomainController $env:COMPUTERNAME
$certNames = @($dc.HostName, $dc.Domain)

# This notification email is contacted if the certificate is close to expiration date.
$notifyEmail = 'dev@gmservice.app'

$certParams = @{
    Domain = $certNames
    PfxPass = $CertPass
    DnsPlugin = 'Cloudflare'
    PluginArgs = $pArgs
    AcceptTOS = $true
    Install = $true
    Contact = $notifyEmail  # optional
    Verbose = $true         # optional
}

New-PACertificate @certParams

# To reuse the certificate for other services let's pull up the hostname. 
$RDCB = [System.Net.Dns]::GetHostByName($env:computerName).HostName
$CertificateRDP =  (Get-PACertificate).PfxFullChain
$Password = ConvertTo-SecureString -String "$CertPass" -AsPlainText -Force

Import-Module RemoteDesktop
Set-RDCertificate -Role RDPublishing -ImportPath $CertificateRDP -Password $Password -ConnectionBroker $RDCB -force
Set-RDCertificate -Role RDWebAccess -ImportPath $CertificateRDP -Password $Password -ConnectionBroker $RDCB -force
Set-RDCertificate -Role RDRedirector -ImportPath $CertificateRDP -Password $Password -ConnectionBroker $RDCB -force
Set-RDCertificate -Role RDGateway -ImportPath $CertificateRDP -Password $Password -ConnectionBroker $RDCB -force
rmbolger commented 8 months ago

Yeah, the blog post was really just about DCs, so it assumed the AD module was already installed on the host you'd be running the script on. Even if you had the AD module installed on a non-DC, that command wouldn't return any results because the computer name wouldn't be a DC. And yeah, there are a number of more generic ways to get the current machine's FQDN. But some machines also have CNAME records pointing to them which you might want in a cert.

If I were trying to detect installed services, I'd probably start with Get-WindowsFeature and see how far it got me.

I haven't touched Remote Access much. So I'm not sure on that one.

fthobe commented 3 months ago

@rmbolger Hey, so I went down the rabbit hole about RDP and made some observations:

Given that this service is used frequently (definetly more than Windows RM) it would be great if it finds it's way into ACME deploy.

fthobe commented 3 months ago

@rmbolger Hey, so I went down the rabbit hole about RDP and made some observations:

  • the rdp listener to access a server does not use the same certificate as the RDP Terminal Server
  • The microsoft procedure is not updated regarding the possibility to retrieve / use the thumbprint
  • I have posted the proper procedure here

Given that this service is used frequently (definetly more than Windows RM) it would be great if it finds it's way into ACME deploy.

Answered fully and completely: https://github.com/rmbolger/Posh-ACME.Deploy/issues/28