Trust is important for a CA, and a CA that makes a mistake should be permanently untrusted. That should have been the case for StartCom, but the Mozilla Foundation was too lenient. Anyways, I cannot bring myself to keep using them and have been slowly transitioning to Let’s Encrypt which got much more usable lately.

For one, the Let’s Encrypt client doesn’t run anywhere; there is simply no way you could run it on the iDRAC server, or at least no easy way. With ACME support though that’s another story. You can generate certs on another machine and validate yourself via a special DNS TXT record, then upload the cert to the target machine.

This time around I decided to play with the ACMESharp, which is a PowerShell module. I went the PowerShell route because I was too lazy to install racadm on a Linux server and that gave me an excuse to try it out. Turns out the library is still kind of a work in progress and the DNS challenge has to be done manually. Or can be scripted.

First, we install ACMESharp: Install-Module -Name ACMESharp -AllowClobber. Next, create a new messy script (depending on experience or lack of thereof).

Import-Module -Name ACMESharp

$domain = "idrac.example.com"
$alias = "idrac$(Get-Date -Format yyy-MM-dd--HH-mm)"

$certname = "idrac$(Get-Date -Format yyy-MM-dd--HH-mm)"
$keyfile = "C:\Admin\Certs\$certname.key"
$crtfile = "C:\Admin\Certs\$certname.crt"
$pfxfile = "C:\Admin\Certs\$certname.pfx"

$racIp = "1.2.3.4"
$racUser = "root"
$racPwd = "calvin"

# initialization
if((Get-ACMEVault) -eq $null) {
    Initialize-ACMEVault
    New-ACMERegistration -Contacts mailto:[email protected] -AcceptTos
}
if((Get-ACMEIdentifier | Where-Object {$_.Alias -eq $alias}).Count -eq 0) {
    New-ACMEIdentifier -Dns $domain -Alias $alias
}

# challenge
$challenge = Complete-ACMEChallenge $alias -ChallengeType dns-01 -Handler manual
$recordName = ($challenge.Challenges | Where-Object {$_.Type -eq "dns-01"}).Challenge.RecordName
$recordValue = ($challenge.Challenges | Where-Object {$_.Type -eq "dns-01"}).Challenge.RecordValue

# update cloudflare
$CFHeaders = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$CFHeaders.Add("X-Auth-Email", "[email protected]")
$CFHeaders.Add("X-Auth-Key", "<cf-auth-key>")

$zone = Invoke-RestMethod -Uri https://api.cloudflare.com/client/v4/zones/<zone-id>/dns_records?per_page=100 -Method Get -ContentType "application/json" -Headers $CFHeaders
$CFRecord = $zone.result | where {$_.name -eq $recordName}

function UpdateDNSRecord($record, $content) {
    if($record.content -eq $content) { return }
    $Data = @{
        id=$record.id
        type=$record.type
        name=$record.name
        content=$content
        zone_id=$record.zone_id
        zone_name=$record.zone_name
        ttl=$record.ttl
    }
    $JsonData = $Data | ConvertTo-Json
    $Uri = 'https://api.cloudflare.com/client/v4/zones/<zone-id>/dns_records/' + $record.id
    Invoke-RestMethod -Uri $Uri -Method Put -ContentType "application/json" -Headers $CFHeaders -Body $JsonData
}
function CreateDNSRecord($name, $content) {
    $Data = @{
        type="TXT"
        name=$name
        content=$content
    }
    $JsonData = $Data | ConvertTo-Json
    $Uri = 'https://api.cloudflare.com/client/v4/zones/<zone-id>/dns_records'
    Invoke-RestMethod -Uri $Uri -Method Post -ContentType "application/json" -Headers $CFHeaders -Body $JsonData
}

if($CFRecord.Count -eq 0) {
    CreateDNSRecord $recordName $recordValue
}
else {
    UpdateDNSRecord $CFRecord $recordValue
}

# submit challenge response
Submit-ACMEChallenge $alias -ChallengeType dns-01

# status
(Update-ACMEIdentifier $alias -ChallengeType dns-01).Challenges | Where-Object {$_.Type -eq "dns-01"}
Update-ACMEIdentifier $alias

# request certificate
New-ACMECertificate $alias -Generate -Alias $certname
Submit-ACMECertificate $certname
Update-ACMECertificate $certname

# export key
Get-ACMECertificate $certname -ExportKeyPEM $keyfile

# export cert
Get-ACMECertificate $certname -ExportCertificatePem $crtfile

# upload to idrac
racadm -r $racIp -u $racUser -p $racPwd sslkeyupload -t 1 -f $keyfile
racadm -r $racIp -u $racUser -p $racPwd sslcertupload -t 1 -f $crtfile

Now, automated renewal seems to still be a work in progress for ACMESharp, so I have resorted to using unique names for the aliases as they recommend doing for now, and having this script run every 89 days since LE certs expire after 90 days. So I’m calling my script a WIP for now too since I’ll have to wait 90 days to see if it actually works fine…

Also I really should consider learning how to write PowerShell modules, with the number of scripts in which I’m using the CloudFlare API…