chef / knife-windows

Plugin for Chef's knife tool for working with Windows nodes
Apache License 2.0
152 stars 110 forks source link

WinRM certificate auth #438

Open jstange opened 7 years ago

jstange commented 7 years ago

The WinRM gem supports certificate authentication. Added some glue to take advantage of it.

My use case invokes bootstraps from Ruby by calling into the library (yeah, I know), so I'm more sure of that than of the actual command-line functionality. I've given the latter little more than a cursory smoke test. It should go something like this:

knife bootstrap windows winrm -t ssl web1.cloudapp.net --winrm-authentication-protocol cert --winrm-client-cert ~/myclient.crt --winrm-client-key ~/myclient.key -f ~/mycert.crt

If it's of use in documentation, below I've pasted an approximation of Powershell we use to get our servers to honor WinRM certificate auth. We don't actually use Administrator internally, but that's probably what most people want, so I've elided that and a few other things.

Set-Item -Path WSMan:\localhost\Service\Auth\Certificate -Value $true

$cacert = Import-Certificate -FilePath $tmp/my-ca-cert.pem -CertStoreLocation Cert:\LocalMachine\Root
$clientcert = Import-Certificate -FilePath $tmp/Administrator-client-cert.pem -CertStoreLocation Cert:\LocalMachine\TrustedPeople

$creds = New-Object System.Management.Automation.PSCredential("Administrator", (ConvertTo-SecureString "mypassword" -AsPlainText -Force))

New-Item -Path WSMan:\localhost\ClientCertificate -Subject 'Administrator@localhost' -URI * -Issuer $cacert.Thumbprint -Force -Credential $creds

Heck, while I'm at it, here's a simplified version of Ruby+OpenSSL that generates our client certificates. The CSR bit:

        key = OpenSSL::PKey::RSA.new 4096
        open("Administrator-client-cert.key", 'w', 0600) { |io|
          io.write key.to_pem
        }
        csr = OpenSSL::X509::Request.new
        csr.version = 3
        csr.subject = OpenSSL::X509::Name.parse "CN=Administrator/O=MyOrgOrWhatever/C=US"
        csr.public_key = key.public_key
        open("Administrator-client-cert.csr", 'w', 0600) { |io|
          io.write csr.to_pem
        }

And the signing by our internal CA:

#  cacert, cakey, and cur_serial loaded in the usual fashion
      csr = OpenSSL::X509::Request.new File.read "Administrator-client-cert.csr"
      key = OpenSSL::PKey::RSA.new File.read "Administrator-client-cert.key"
      sans = ["otherName:1.3.6.1.4.1.311.20.2.3;UTF8:Administrator@localhost"]
      cert = OpenSSL::X509::Certificate.new
      cert.serial = cur_serial
      cert.version = 3
      cert.not_before = Time.now
      cert.not_after = Time.now + 180000000
      cert.subject = csr.subject
      cert.public_key = csr.public_key
      cert.issuer = cacert.subject
      ef = OpenSSL::X509::ExtensionFactory.new
      ef.issuer_certificate = cacert
      ef.subject_certificate = cert
      ef.subject_request = csr
      cert.add_extension(ef.create_extension("keyUsage","nonRepudiation,digitalSignature,keyEncipherment", false))
      cert.add_extension(ef.create_extension("subjectAltName",sans.join(","),false))
      cert.add_extension(ef.create_extension("extendedKeyUsage","clientAuth,serverAuth,codeSigning,emailProtection",false))
      cert.sign cakey, OpenSSL::Digest::SHA256.new