diff --git a/REFERENCE.md b/REFERENCE.md index 8f2a2319e..e8b419ea5 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -6,6 +6,8 @@ ### Classes +#### Public Classes + * [`puppet_agent`](#puppet_agent): Upgrades Puppet 4 and newer to the requested version. * [`puppet_agent::configure`](#puppet_agent--configure): Uses $puppet_agent::config to manage settings in puppet.conf. * [`puppet_agent::install`](#puppet_agent--install): This class is called from puppet_agent for install. @@ -22,10 +24,13 @@ * [`puppet_agent::osfamily::windows`](#puppet_agent--osfamily--windows): Determines the puppet-agent package location for Windows OSes. * [`puppet_agent::params`](#puppet_agent--params): Sets variables according to platform. * [`puppet_agent::prepare`](#puppet_agent--prepare): This class is called from puppet_agent to prepare for the upgrade. -* [`puppet_agent::prepare::package`](#puppet_agent--prepare--package): Ensures correct puppet-agent package is downloaded locally. * [`puppet_agent::prepare::puppet_config`](#puppet_agent--prepare--puppet_config): Private class called from puppet_agent::prepare class. * [`puppet_agent::service`](#puppet_agent--service): Ensures that managed services are running. +#### Private Classes + +* `puppet_agent::prepare::package`: Ensures correct puppet-agent package is downloaded locally. + ### Resource types * [`puppet_agent_end_run`](#puppet_agent_end_run): Stops the current Puppet run if a puppet-agent upgrade was performed. Used on platforms that manage the Puppet Agent upgrade with a package r @@ -614,24 +619,6 @@ The puppet-agent version to install. Default value: `undef` -### `puppet_agent::prepare::package` - -for installation. This is used on platforms without package managers capable of -working with a remote https repository. - -#### Parameters - -The following parameters are available in the `puppet_agent::prepare::package` class: - -* [`source`](#-puppet_agent--prepare--package--source) - -##### `source` - -Data type: `Variant[String, Array]` - -The source file for the puppet-agent package. Can use any of the data types -and protocols that the File resource's source attribute can. - ### `puppet_agent::prepare::puppet_config` Private class called from puppet_agent::prepare class. @@ -993,6 +980,18 @@ Data type: `Optional[Integer]` The number of retries in case of network connectivity failures +##### `username` + +Data type: `Optional[String[1]]` + +The username to use when downloading from a source location requiring authentication + +##### `password` + +Data type: `Optional[Sensitive[String[1]]]` + +The password to use when downloading from a source location requiring authentication + ### `install_shell` Install the Puppet agent package @@ -1063,13 +1062,13 @@ The number of retries in case of network connectivity failures ##### `username` -Data type: `Optional[String]` +Data type: `Optional[String[1]]` The username to use when downloading from a source location requiring authentication ##### `password` -Data type: `Optional[String]` +Data type: `Optional[Sensitive[String[1]]]` The password to use when downloading from a source location requiring authentication diff --git a/manifests/init.pp b/manifests/init.pp index 9fad7b6b5..502f19533 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -3,7 +3,9 @@ # @param arch # The package architecture. Defaults to the architecture fact. # @param collection -# The Puppet Collection to track. Defaults to 'PC1'. +# The Puppet Collection to track. Defaults to 'PC1'. Valid values are puppet7, +# puppet8, puppet, puppet7-nightly, puppet8-nightly, puppet-nightly, +# puppetcore7, puppetcore8. # @param is_pe # Install from Puppet Enterprise repos. Enabled if communicating with a PE master. # @param manage_pki_dir diff --git a/manifests/osfamily/windows.pp b/manifests/osfamily/windows.pp index 8114ddeb0..98ed62ae8 100644 --- a/manifests/osfamily/windows.pp +++ b/manifests/osfamily/windows.pp @@ -23,13 +23,22 @@ } else { if $puppet_agent::collection == 'PC1' { $source = "${puppet_agent::windows_source}/windows/${puppet_agent::package_name}-${puppet_agent::prepare::package_version}-${puppet_agent::arch}.msi" + } elsif $puppet_agent::collection =~ /core/ { + $source = 'https://artifacts-puppetcore.puppet.com/v1/download' } else { $source = "${puppet_agent::windows_source}/windows/${puppet_agent::collection}/${puppet_agent::package_name}-${puppet_agent::prepare::package_version}-${puppet_agent::arch}.msi" } } + $destination_name = if $puppet_agent::collection =~ /core/ { + "${puppet_agent::package_name}-${puppet_agent::prepare::package_version}-${puppet_agent::arch}.msi" + } else { + undef + } + class { 'puppet_agent::prepare::package': - source => $source, + source => $source, + destination_name => $destination_name, } contain puppet_agent::prepare::package diff --git a/manifests/prepare/package.pp b/manifests/prepare/package.pp index dee0afa29..a1b1e549f 100644 --- a/manifests/prepare/package.pp +++ b/manifests/prepare/package.pp @@ -2,11 +2,10 @@ # for installation. This is used on platforms without package managers capable of # working with a remote https repository. # -# @param source -# The source file for the puppet-agent package. Can use any of the data types -# and protocols that the File resource's source attribute can. +# @api private class puppet_agent::prepare::package ( Variant[String, Array] $source, + Optional[String[1]] $destination_name = undef ) { assert_private() @@ -14,12 +13,17 @@ ensure => directory, } - # In order for the 'basename' function to work correctly we need to change - # any \s to /s (even for windows UNC paths) so that it will correctly pull off - # the filename. Since this operation is only grabbing the base filename and not - # any part of the path this should be safe, since the source will simply remain - # what it was before and we can still pull off the filename. - $package_file_name = basename(regsubst($source, "\\\\", '/', 'G')) + $package_file_name = if $destination_name { + $destination_name + } else { + # In order for the 'basename' function to work correctly we need to change + # any \s to /s (even for windows UNC paths) so that it will correctly pull off + # the filename. Since this operation is only grabbing the base filename and not + # any part of the path this should be safe, since the source will simply remain + # what it was before and we can still pull off the filename. + basename(regsubst($source, "\\\\", '/', 'G')) + } + if $facts['os']['family'] =~ /windows/ { $local_package_file_path = windows_native_path("${puppet_agent::params::local_packages_dir}/${package_file_name}") $mode = undef @@ -28,12 +32,38 @@ $mode = '0644' } - file { $local_package_file_path: - ensure => file, - owner => $puppet_agent::params::user, - group => $puppet_agent::params::group, - mode => $mode, - source => $source, - require => File[$puppet_agent::params::local_packages_dir], + if $puppet_agent::collection =~ /core/ and $facts['os']['family'] =~ /windows/ { + $download_username = getvar('puppet_agent::username', 'forge-key') + $download_password = unwrap(getvar('puppet_agent::password')) + $dev = count(split($puppet_agent::prepare::package_version, '\.')) > 3 + + $_download_puppet = windows_native_path("${facts['env_temp_variable']}/download_puppet.ps1") + file { $_download_puppet: + ensure => file, + content => Sensitive(epp('puppet_agent/download_puppet.ps1.epp')), + } + + exec { 'Download Puppet Agent': + command => [ + "${facts['os']['windows']['system32']}\\WindowsPowerShell\\v1.0\\powershell.exe", + '-ExecutionPolicy', + 'Bypass', + '-NoProfile', + '-NoLogo', + '-NonInteractive', + $_download_puppet + ], + creates => $local_package_file_path, + require => File[$puppet_agent::params::local_packages_dir], + } + } else { + file { $local_package_file_path: + ensure => file, + owner => $puppet_agent::params::user, + group => $puppet_agent::params::group, + mode => $mode, + source => $source, + require => File[$puppet_agent::params::local_packages_dir], + } } } diff --git a/metadata.json b/metadata.json index d3e2f5152..f49b300a2 100644 --- a/metadata.json +++ b/metadata.json @@ -23,6 +23,10 @@ { "name": "puppetlabs-facts", "version_requirement": ">= 0.5.0 < 2.0.0" + }, + { + "name": "puppetlabs-powershell", + "version_requirement": ">= 6.0.2 < 7.0.0" } ], "operatingsystem_support": [ diff --git a/tasks/install_powershell.json b/tasks/install_powershell.json index 868a95c65..65b0c8bc2 100644 --- a/tasks/install_powershell.json +++ b/tasks/install_powershell.json @@ -42,6 +42,14 @@ "description": "The number of retries in case of network connectivity failures", "type": "Optional[Integer]", "default": 5 + }, + "username": { + "description": "The username to use when downloading from a source location requiring authentication", + "type": "Optional[String[1]]" + }, + "password": { + "description": "The password to use when downloading from a source location requiring authentication", + "type": "Optional[Sensitive[String[1]]]" } }, "supports_noop": true diff --git a/tasks/install_powershell.ps1 b/tasks/install_powershell.ps1 index 679baa07b..ce67f2eec 100644 --- a/tasks/install_powershell.ps1 +++ b/tasks/install_powershell.ps1 @@ -7,11 +7,21 @@ Param( [String]$install_options = 'REINSTALLMODE="amus"', [Bool]$stop_service = $False, [Int]$retry = 5, - [Bool]$_noop = $False + [Bool]$_noop = $False, + [String]$username = 'forge-key', + [String]$password ) # If an error is encountered, the script will stop instead of the default of "Continue" $ErrorActionPreference = "Stop" +try { + $os_version = (Get-WmiObject Win32_OperatingSystem).Version +} +catch [System.Management.Automation.CommandNotFoundException] { + $os_version = (Get-CimInstance -ClassName win32_OperatingSystem).Version +} +$major_os_version = ($os_version -split '\.')[0] + try { if ((Get-WmiObject Win32_OperatingSystem).OSArchitecture -match '^32') { $arch = "x86" @@ -27,9 +37,19 @@ catch [System.Management.Automation.CommandNotFoundException] { } } +$fips = 'false' +try { + if ((Get-ItemPropertyValue -Path 'HKLM:\System\CurrentControlSet\Control\Lsa\FipsAlgorithmPolicy' -Name Enabled) -ne 0) { + $fips = 'true' + } +} +catch { + Write-Output "Failed to lookup FIPS mode, assuming it is disabled" +} + function Test-PuppetInstalled { $rootPath = 'HKLM:\SOFTWARE\Puppet Labs\Puppet' - try { + try { if (Get-ItemProperty -Path $rootPath) { RETURN $true } } catch { @@ -98,12 +118,21 @@ if (Test-RunningServices) { # Change windows_source only if the collection is a nightly build, and the source was not explicitly specified. if (($collection -like '*nightly*') -And -Not ($PSBoundParameters.ContainsKey('windows_source'))) { $windows_source = 'https://nightlies.puppet.com/downloads' +} elseif (($collection -like '*puppetcore*') -And -Not ($PSBoundParameters.ContainsKey('windows_source'))) { + $windows_source = 'https://artifacts-puppetcore.puppet.com/v1/download' } if ($absolute_source) { $msi_source = "$absolute_source" -} -else { +} elseif ($collection -like '*puppetcore*') { + # dev param is case-sensitive, so don't use $True + if (($version -split '\.').count -gt 3) { + $dev = '&dev=true' + } else { + $dev = '' + } + $msi_source = "${windows_source}?version=${version}&os_name=windows&os_version=${major_os_version}&os_arch=${arch}&fips=${fips}${dev}" +} else { $msi_source = "$windows_source/windows/${collection}/${msi_name}" } @@ -125,15 +154,19 @@ function Set-Tls12 { } function DownloadPuppet { - Write-Output "Downloading the Puppet Agent installer on $env:COMPUTERNAME..." + Write-Output "Downloading the Puppet Agent installer on $env:COMPUTERNAME from ${msi_source}" Set-Tls12 $webclient = New-Object system.net.webclient - + if ($password) { + $credentials = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes("${username}:${password}")) + $webclient.Headers.Add("Authorization", "Basic ${credentials}") + } try { $webclient.DownloadFile($msi_source,$msi_dest) } catch [System.Net.WebException] { + Write-Host "Download exception: $($_.Exception.Message)" For ($attempt_number = 1; $attempt_number -le $retry; $attempt_number++) { try { Write-Output "Retrying... [$attempt_number/$retry]" @@ -141,6 +174,7 @@ function DownloadPuppet { break } catch [System.Net.WebException] { + Write-Host "Download exception: $($_.Exception.Message)" if($attempt_number -eq $retry) { # If we can't find the msi, then we may not be configured correctly if($_.Exception.Response.StatusCode -eq [system.net.httpstatuscode]::NotFound) { diff --git a/tasks/install_shell.json b/tasks/install_shell.json index 0fc475e06..b31139ab4 100644 --- a/tasks/install_shell.json +++ b/tasks/install_shell.json @@ -46,11 +46,11 @@ }, "username": { "description": "The username to use when downloading from a source location requiring authentication", - "type": "Optional[String]" + "type": "Optional[String[1]]" }, "password": { "description": "The password to use when downloading from a source location requiring authentication", - "type": "Optional[String]" + "type": "Optional[Sensitive[String[1]]]" } }, "files": ["facts/tasks/bash.sh"], diff --git a/templates/download_puppet.ps1.epp b/templates/download_puppet.ps1.epp new file mode 100644 index 000000000..43a04c3df --- /dev/null +++ b/templates/download_puppet.ps1.epp @@ -0,0 +1,20 @@ +$body = @{ + "version" = "<%= $puppet_agent::prepare::package_version %>" + "dev" = "<%= $puppet_agent::prepare::package::dev %>" + "os_name" = "<%= $facts['os']['family'] %>" + "os_version" = "<%= $facts['os']['release']['major'] %>" + "os_arch" = "<%= $facts['os']['architecture'] %>" + "fips" = "<%= $facts['fips_enabled'] %>" +} +$username = "<%= $puppet_agent::prepare::package::download_username %>" +$password = ConvertTo-SecureString "<%= $puppet_agent::prepare::package::download_password %>" -AsPlainText -Force +$credential = New-Object System.Management.Automation.PSCredential($username, $password) +try { + Invoke-WebRequest -Uri "<%= $puppet_agent::prepare::package::source %>" ` + -Body $body ` + -Credential $credential ` + -OutFile "<%= $puppet_agent::prepare::package::local_package_file_path %>" +} catch [System.Net.WebException] { + Write-Host "Network-related error: $($_.Exception.Message)" + exit 1 +}