How to set up WinRM over HTTPS with Puppet

Ever since I started working with Bolt to automate tasks against Linux and Windows servers, I made a mental note that for my Windows servers, I was going to implement WinRM over HTTPS some day. That --no-ssl command line parameter for Bolt was a daily reminder to get it done. Fortunately, it turns out there are a couple of Forge modules available that make setting this up a breeze! So I thought I’d write a post to share this with you all.

Deciding which certificates to use

The most important step is to decide what certificates should be used on the servers. Every server is going to require a valid local certificate with the Subject Name matching the server's FQDN and the certificate issued by a trusted authority. If you don't have your own PKI infrastructure today, that can sure sound like a daunting prospect. Many articles for enabling WinRM over HTTPS will have you generate self-signed certificates on the servers and then basically disable all SSL verification checks on the client to be able to connect. You can do this with Bolt too by using --no-ssl-verify. But in that case I would be replacing --no-ssl with--no-ssl-verify, which is 7 letters more to type....

We can do better than that. If you have a PKI infrastructure in place today that you'd like to leverage, simply skip to Step 2. If not, keep on reading.

If you're running a Puppet master, you actually already have a PKI platform that can be used! The Puppet master takes care of everything we need for this to work seamlessly:

  • The master runs a Certificate Authority that generates certificates for all managed servers
  • The certificates have a Subject Name that (by default) is identical to the server's FQDN
  • The master takes care of automatically replacing expired certificates on managed servers
  • All certificates can be trusted by simply trusting the Puppet master CA certificates

So, all we need for the Puppet PKI platform to be used for WinRM is to import the Puppet certificate to each server's local certificate store and add the Puppet master CA certificates to list of the Trusted Root Certificates.

Step 1: Adding the Puppet certificates to the local certificate store

We provide a module to automate this: the puppetlabs/windows_puppet_certificates module. After adding the module to your Puppetfile, you'll see a handy new puppet_cert_paths fact get reported, which will show the locations of Puppet certificates on the server. >Make sure you’re using the latest 0.2.1 version of the module, as this contains a required fix.

The module also provides a class to automatically import the Puppet certificates into the certificate store. Let's build a manifest for our WinRM configuration:

site-modules/profile/manifests/winrm_config.pp

1
2
3
4
5
6
7
8
class profile::winrm_config {

    # Add Puppet CA and the node's certificate to the local store
    class { 'windows_puppet_certificates':
        manage_master_cert => true,
        manage_client_cert => true,
    }
}

Step 2: Configuring WinRM with the HTTPS listener

Next, we can configure WinRM with a new HTTPS listener, and yes, there's a module for that too. :-)

The nekototori/winrmssl module provides a winrmssl resource that gives us everything we need. The resource works as follows:

  • You provide the resource with the name (DN) of a certificate issuer
  • The resource then looks for local certificates in the certificate store that have a Subject Name that matches the FQDN of the system and have been issued by the certificate issuer mentioned above
  • If such a certificate is found, WinRM is configured with a HTTPS listener based on this certificate

It can be used in two ways:

  1. You can specify the value (DN) of the certificate issuer to use. This is meant for using an existing PKI infrastructure.
  2. You can specify the path to a PEM certificate of a CA, and it will extract the 'Issued By' details from that certificate automatically. This is meant for using the Puppet CA platform.

For an existing PKI infrastructure, the Puppet code for this module would look like this:

winrmssl {'My PKI': ensure => present, issuer => 'CN=Example Issuer CA Authority, OU=Example Corp, OU=Test', disable_http => false }

For your Puppet CA platform, the Puppet code for this module would look like this:

1
2
3
4
5
6
winrmssl {'Puppet Enterprise CA':
    ensure       => present,
    issuer       => $facts['puppet_cert_paths']['ca_path'],
    disable_http => false,
    require      => Class['windows_puppet_certificates']
}

In my case, I'm using the Puppet CA, so let's update the winrm_config manifest with it:

site-modules/profile/manifests/winrm_config.pp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class profile::winrm_config {

    # Add Puppet CA and the node's certificate to the local store
    class { 'windows_puppet_certificates':
        manage_master_cert => true,
        manage_client_cert => true,
    }

    # Configure HTTPS for WinRM, using the imported Puppet node certificate
    # Use $facts['puppet_cert_paths']['ca_path'] to auto-select the Puppet CA as the certificate issuer
    winrmssl {'Puppet Enterprise CA':
        ensure       => present,
        issuer       => $facts['puppet_cert_paths']['ca_path'],
        disable_http => false,
        require      => Class['windows_puppet_certificates']
    }
}

Step 3: Opening the firewall and limiting the use of the HTTP listener

The new HTTPS listener runs on port TCP 5986, which needs to be opened up on the Windows firewall. We can use the geoffwilliams/windows_firewall module for that.

At the same time, you probably saw the disable_http => false parameter in the previous step. You might be wondering: "Why aren't you disabling HTTP altogether for WinRM?" The truth is, you’re likely to need it if you're using PowerShell DSC. And if you're using Puppet to manage Windows, it's likely that you're already using some PowerShell DSC, or that you will use it in the future. The invoke-dscresource command does not support SSL and thus will break if we completely disable the HTTP listener. But we can limit its surface and make this a non-issue by changing the firewall rule for the HTTP listener to only allow connections to 127.0.0.1. This way, PowerShell DSC will still work while nothing else can use the HTTP listener.

Let's add both firewall rules to our winrm_config manifest:

site-modules/profile/manifests/winrm_config.pp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class profile::winrm_config {

    # Add Puppet CA and the node's certificate to the local store
    class { 'windows_puppet_certificates':
        manage_master_cert => true,
        manage_client_cert => true,
    }

    # Configure HTTPS for WinRM, using the imported Puppet node certificate
    # Use $facts['puppet_cert_paths']['ca_path'] to auto-select the Puppet CA as the certificate issuer
    winrmssl {'Puppet Enterprise CA':
        ensure       => present,
        issuer       => $facts['puppet_cert_paths']['ca_path'],
        disable_http => false,
        require      => Class['windows_puppet_certificates']
    }

    windows_firewall_rule {
        default:
            ensure   => present,
            action   => 'allow',
            protocol => 'tcp',
            profile  => ['public','private','domain'],
        ;
        'Windows Remote Management (HTTPS-In)':
            local_port  => '5986',
            description => 'Inbound rule for Windows Remote Management via WS-Management. [TCP 5986]',
        ;
        'Windows Remote Management (HTTP-In)':
            local_port    => '5985',
            local_address => '127.0.0.1',
            description   => 'Restricted inbound rule for local Management via WS-Management. [TCP 5985]',
    }
}

Now classify all nodes with the above manifest and do a Puppet run to enforce the changes!

Step 4: Testing WinRM over HTTPS with PowerShell

Once the nodes have run Puppet, let's see if we can seamlessly create a remote PowerShell session from another Windows server. Log into one of your managed servers, open a PowerShell session, and enter:

Enter-PSSession -ComputerName <fqdn of another server> -UseSSL

If all works well, this should just work:

1
2
PS C:\Users\administrator> enter-pssession -computername server2.puppet.local -UseSSL
[server2.puppet.local]: PS C:\Users\administrator\Documents>

Step 5: Testing WinRM over HTTPS with Bolt

And finally, let’s tell Bolt to now use SSL for WinRM! By default, Bolt does not have a list of CAs it trusts. You need to set a cacert parameter in either bolt.yaml or in your inventory.yaml to inform Bolt which CA(s) to trust.

To do this, you need a copy of the ca.pem from the Puppet master. If you're running Bolt on any server that is already managed by that Puppet master, you can find this file here:

  • Windows: C:\ProgramData\Puppetlabs\puppet\etc\ssl\certs\ca.pem
  • Linux: /etc/puppetlabs/puppet/ssl/certs/ca.pem

So, assuming we have a copy of ca.pem in one of those locations, we can use that in our bolt.yaml. Let's try it on the same Windows server that we used in Step 4:

  • Create a bolt.yaml in .puppetlabs\bolt in your home directory (~/.puppetlabs/bolt/bolt.yaml, where ~ is identical to %HOMEDRIVE%%HOMEPATH%)
  • Add this winrm section to your bolt.yaml:
1
2
winrm:
  cacert: C:\ProgramData\Puppetlabs\puppet\etc\ssl\certs\ca.pem

Now we can run Bolt. Let's try it:

1
2
3
4
5
6
7
8
PS C:\Users\administrator> bolt command run 'hostname' --targets winrm://server2.puppet.local --user administrator --password-prompt
Please enter your password:
Started on winrm://server2.puppet.local...
Finished on winrm://server2.puppet.local:
  STDOUT:
    server2
Successful on 1 node: winrm://server2.puppet.local
Ran on 1 node in 1.62 sec

No more --no-ssl, yay!

Learn more

Kevin Reeuwijk is a principal sales engineer at Puppet.

Puppet sites use proprietary and third-party cookies. By using our sites, you agree to our cookie policy.