Skip to content

Commit

Permalink
Enh ssl module (#267)
Browse files Browse the repository at this point in the history
* Initial commit

* Functional component

* Adding Pester tests

* Adding tests to new resource

* Had some issues in tests

* Fixed linting errors and added example

* Fixed tests for all authentication methods.  The global variable of '' seemed to be unavailable in the BeforeAll, added path resolution here and it worked.

* Implementing suggested changes.

* More changes

* Revert "Fixed tests for all authentication methods.  The global variable of '' seemed to be unavailable in the BeforeAll, added path resolution here and it worked."

This reverts commit 6beee01.

* Implementing suggested changes.

* Changing cOctopusServerSslCertificate.Tests from Context to Describe blocks.

* Adding readme for new resource

* Adding referenced to new readme from main readme

* Adding e2e tests for OctopusServerSslCertificate resource.

* Aligning code, removed commented out code.

* Update OctopusDSC/DSCResources/cOctopusServerSslCertificate/cOctopusServerSslCertificate.psm1

Co-authored-by: Matt Richardson <[email protected]>

* Update OctopusDSC/DSCResources/cOctopusServerSslCertificate/cOctopusServerSslCertificate.psm1

Co-authored-by: Matt Richardson <[email protected]>

* Update OctopusDSC/Tests/cOctopusServerSslCertificate.Tests.ps1

Co-authored-by: Matt Richardson <[email protected]>

* Update README-cOctopusServerSslCertificate.md

Co-authored-by: Matt Richardson <[email protected]>

* Update README-cOctopusServerSslCertificate.md

Co-authored-by: Matt Richardson <[email protected]>

* Update OctopusDSC/Examples/cOctopusServerSslCertificate.ps1

Co-authored-by: Matt Richardson <[email protected]>

Co-authored-by: Matt Richardson <[email protected]>
  • Loading branch information
twerthi and matt-richardson authored Apr 21, 2021
1 parent fc0eef8 commit f1adca6
Show file tree
Hide file tree
Showing 11 changed files with 403 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# dot-source the helper file (cannot load as a module due to scope considerations)
. (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) -ChildPath 'OctopusDSCHelpers.ps1')

# This is the Octopus Deploy server Application ID, declared within Octopus Server itself
$octopusServerApplicationId = "{E2096A4C-2391-4BE1-9F17-E353F930E7F1}"

function Get-CurrentSSLBinding {
param([string] $ApplicationId,
[string]$Port)

$certificateBindings = (& netsh http show sslcert) | select-object -skip 3 | out-string
$newLine = [System.Environment]::NewLine
$certificateBindings = $certificateBindings -split "$newLine$newLine"
$certificateBindingsList = foreach ($certificateBinding in $certificateBindings) {
if ($certificateBinding -ne "") {
$certificateBinding = $certificateBinding -replace " ", "" -split ": "
[pscustomobject]@{
IPPort = ($certificateBinding[1] -split "`n")[0]
CertificateThumbprint = ($certificateBinding[2] -split "`n" -replace '[^a-zA-Z0-9]', '')[0]
AppID = ($certificateBinding[3] -split "`n")[0]
CertStore = ($certificateBinding[4] -split "`n")[0]
}
}
}

return ($certificateBindingsList | Where-Object {($_.AppID.Trim() -eq $ApplicationId) -and ($_.IPPort.Trim() -eq "{0}:{1}" -f "0.0.0.0", $Port) })
}

function Get-TargetResource {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSDSCUseVerboseMessageInDSCResource", "")]
param (
[Parameter(Mandatory)]
[string] $InstanceName,
[ValidateSet("Present", "Absent")]
[string] $Ensure = "Present",
[ValidateNotNullOrEmpty()]
[Parameter(Mandatory)]
[string] $Thumbprint,
[ValidateNotNullOrEmpty()]
[Parameter(Mandatory)]
[string] $StoreName,
[ValidateNotNullOrEmpty()]
[int] $Port = 443
)

$existingSSLConfig = (Get-CurrentSSLBinding -ApplicationId $octopusServerApplicationId -Port $Port -IPAddress $IPAddress)

if ($null -ne $existingSSLConfig) {
$existingSSLPort = [int](($existingSSLConfig.IPPort).Split(":")[1])
$existingSSLThumbprint = $existingSSLConfig.CertificateThumbprint.Trim()
$existingSSLCertificateStoreName = $existingSSLConfig.CertStore.Trim()
$existingEnsure = "Present"
}
else {
$existingEnsure = "Absent"
}

$result = @{
InstanceName = $InstanceName
Ensure = $existingEnsure
Port = $existingSSLPort
StoreName = $existingSSLCertificateStoreName
Thumbprint = $existingSSLThumbprint
}

return $result
}

function Test-TargetResource {
param(
[Parameter(Mandatory)]
[string] $InstanceName,
[ValidateSet("Present", "Absent")]
[string] $Ensure = "Present",
[ValidateNotNullOrEmpty()]
[Parameter(Mandatory)]
[string] $Thumbprint,
[ValidateNotNullOrEmpty()]
[Parameter(Mandatory)]
[string] $StoreName,
[ValidateNotNullOrEmpty()]
[int] $Port = 443
)

$currentResource = (Get-TargetResource @PSBoundParameters)
$params = Get-ODSCParameter $MyInvocation.MyCommand.Parameters

$currentConfigurationMatchesRequestedConfiguration = $true
foreach ($key in $currentResource.Keys) {
$currentValue = $currentResource.Item($key)
$requestedValue = $params.Item($key)

if ($currentValue -ne $requestedValue) {
Write-Verbose "(FOUND MISMATCH) Configuration parameter '$key' with value '$currentValue' mismatched the specified value '$requestedValue'"
$currentConfigurationMatchesRequestedConfiguration = $false
}
else {
Write-Verbose "Configuration parameter '$key' matches the requested value '$requestedValue'"
}
}

return $currentConfigurationMatchesRequestedConfiguration
}

function Set-TargetResource {
param(
[Parameter(Mandatory)]
[string] $InstanceName,
[ValidateSet("Present", "Absent")]
[string] $Ensure = "Present",
[ValidateNotNullOrEmpty()]
[Parameter(Mandatory)]
[string] $Thumbprint,
[ValidateNotNullOrEmpty()]
[Parameter(Mandatory)]
[string] $StoreName,
[ValidateNotNullOrEmpty()]
[int] $Port = 443
)

if ($Ensure -eq "Present") {
$exeArgs = @(
'ssl-certificate',
'--instance', $Instancename,
'--thumbprint', $Thumbprint,
'--certificate-store', $StoreName,
'--port', $Port
)

Write-Verbose "Binding certificate ..."
Invoke-OctopusServerCommand $exeArgs
}
else {
$currentBinding = (Get-CurrentSSLBinding -ApplicationId $octopusServerApplicationId -Port $Port -IPAddress $IPAddress)
if ($null -ne $currentBinding) {
Write-Verbose "Removing certificate binding ..."
# ideally, we'd call a command in Octopus.Server for this, but there's no command to do this (at this point)
& netsh http delete sslcert ("{0}:{1}" -f "0.0.0.0", $Port)
$currentBinding = (Get-CurrentSSLBinding -ApplicationId $octopusServerApplicationId -Port $Port -IPAddress $IPAddress)

if ($null -eq $currentBinding) {
Write-Verbose "Binding successfully removed."
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[ClassVersion("1.0.0"), FriendlyName("cOctopusServerSslCertificate")]
class cOctopusServerSslCertificate : OMI_BaseResource
{
[Key, Description("Name of the Octopus Server instance")] string InstanceName;
[Write, Required, Description("Thumbprint of the certificate")] string Thumbprint;
[Write, ValueMap{"Present", "Absent"}, Values{"Present", "Absent"}] string Ensure;
[Write, Required, ValueMap{"My", "WebHosting"}, Values{"My", "WebHosting"}] string StoreName;
[Write] uint16 Port;
};
18 changes: 18 additions & 0 deletions OctopusDSC/Examples/cOctopusServerSslCertificate.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Binds an SSL certificate to the Octopus Server to allow Octopus to listen over https

Configuration SampleConfig
{
Import-DscResource -Module OctopusDSC

Node "localhost"
{
cOctopusServerSslCertificate "Bind SSL Certifiate"
{
InstanceName = "OctopusServer"
Thumbprint = "c42a148bcd3959101f2e7a3d76edb924bff84b6b"
Ensure = "Present"
StoreName = "My"
Port = 443
}
}
}
2 changes: 1 addition & 1 deletion OctopusDSC/OctopusDSC.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ CmdletsToExport = @()
AliasesToExport = @()

# DSC resources to export from this module
DscResourcesToExport = @('cOctopusEnvironment', 'cOctopusSeqLogger', 'cOctopusServer', 'cOctopusServerActiveDirectoryAuthentication', 'cOctopusServerAzureADAuthentication', 'cOctopusServerGoogleAppsAuthentication', 'cOctopusServerGuestAuthentication', 'cOctopusServerOktaAuthentication', 'cOctopusServerSpace', 'cOctopusServerUsernamePasswordAuthentication', 'cOctopusServerWatchdog', 'cOctopusWorkerPool', 'cTentacleAgent', 'cTentacleWatchdog')
DscResourcesToExport = @('cOctopusEnvironment', 'cOctopusSeqLogger', 'cOctopusServer', 'cOctopusServerActiveDirectoryAuthentication', 'cOctopusServerAzureADAuthentication', 'cOctopusServerGoogleAppsAuthentication', 'cOctopusServerGuestAuthentication', 'cOctopusServerOktaAuthentication', 'cOctopusServerSpace', 'cOctopusServerUsernamePasswordAuthentication', 'cOctopusServerWatchdog', 'cOctopusWorkerPool', 'cTentacleAgent', 'cTentacleWatchdog', 'cOctopusServerSslCertificate')

# List of all modules packaged with this module
# ModuleList = @()
Expand Down
131 changes: 131 additions & 0 deletions OctopusDSC/Tests/cOctopusServerSslCertificate.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
#requires -Version 4.0
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] # these are tests, not anything that needs to be secure
param()

$moduleName = Split-Path ($PSCommandPath -replace '\.Tests\.ps1$', '') -Leaf
$modulePath = Split-Path $PSCommandPath -Parent
$modulePath = Resolve-Path "$PSCommandPath/../../DSCResources/$moduleName/$moduleName.psm1"
$module = $null

try
{
$prefix = [guid]::NewGuid().Guid -replace '-'
$module = Import-Module $modulePath -Prefix $prefix -PassThru -ErrorAction Stop

InModuleScope $module.Name {
$sampleConfigPath = Split-Path $PSCommandPath -Parent
$sampleConfigPath = Join-Path $sampleConfigPath "SampleConfigs"

Describe 'cOctopusServerSslCertificate' {
Describe 'Get-TargetResource - when present and port not specified' {
It 'Returns correct data when SSL binding exists' {
Mock Get-CurrentSSLBinding {
return [PSCustomObject]@{
IPPort = "0.0.0.0:443"
CertificateThumbprint = "dfcbdb879e5315e05982c05793e57c39241797e3"
AppID = "{E2096A4C-2391-4BE1-9F17-E353F930E7F1}"
CertStore = "My"
}
}

$result = Get-TargetResource -InstanceName "OctopusServer" `
-Ensure "Present" `
-Thumbprint "dfcbdb879e5315e05982c05793e57c39241797e3" `
-StoreName "My"

$result.Ensure | Should -Be 'Present'
$result.Port | Should -Be "443"
$result.StoreName | Should -Be "My"
$result.Thumbprint | Should -Be "dfcbdb879e5315e05982c05793e57c39241797e3"
}
}

Describe 'Get-TargetResource - when present and port specified' {
It 'Returns present and 1443 when SSL binding exists' {
Mock Get-CurrentSSLBinding {
return [PSCustomObject]@{
IPPort = "0.0.0.0:1443"
CertificateThumbprint = "dfcbdb879e5315e05982c05793e57c39241797e3"
AppID = "{E2096A4C-2391-4BE1-9F17-E353F930E7F1}"
CertStore = "My"
}
}

$result = Get-TargetResource -InstanceName "OctopusServer" `
-Ensure "Present" `
-Thumbprint "dfcbdb879e5315e05982c05793e57c39241797e3" `
-StoreName "My" `
-Port "1443"

$result.Ensure | Should -Be 'Present'
$result.Port | Should -Be "1443"
$result.StoreName | Should -Be "My"
$result.Thumbprint | Should -Be "dfcbdb879e5315e05982c05793e57c39241797e3"
}
}

Describe 'Get-TargetResource - when absent' {
It 'Returns absent when binding does not exist' {
Mock Get-CurrentSSLBinding {
return $null
}

$result = Get-TargetResource -InstanceName "OctopusServer" `
-Ensure "Present" `
-Thumbprint "dfcbdb879e5315e05982c05793e57c39241797e3" `
-StoreName "My"

$result.Ensure | Should -Be "Absent"
$result.StoreName | Should -Be $null
$result.Thumbprint | Should -Be $null
$result.Port | Should -Be $null
}
}

Describe 'Test-TargetResource - when present' {
It 'Returns false when port differs' {
Mock Get-CurrentSSLBinding {
return [PSCustomObject]@{
IPPort = "0.0.0.0:443"
CertificateThumbprint = "dfcbdb879e5315e05982c05793e57c39241797e3"
AppID = "{E2096A4C-2391-4BE1-9F17-E353F930E7F1}"
CertStore = "My"
}

$result = Test-TargetResource -InstanceName "OctopusServer" `
-Ensure "Present" `
-Thumbprint "dfcbdb879e5315e05982c05793e57c39241797e3" `
-StoreName "My" `
-Port "1443"

$result | Should -Be $false
}
}

It 'Returns true when port matches' {
return [PSCustomObject]@{
IPPort = "0.0.0.0:443"
CertificateThumbprint = "dfcbdb879e5315e05982c05793e57c39241797e3"
AppID = "{E2096A4C-2391-4BE1-9F17-E353F930E7F1}"
CertStore = "My"
}

$result = Test-TargetResource -InstanceName "OctopusServer" `
-Ensure "Present" `
-Thumbprint "dfcbdb879e5315e05982c05793e57c39241797e3" `
-StoreName "My" `
-Port "443"

$result | Should -Be $true
}
}
}
}
}
finally
{
if ($module)
{
Remove-Module -ModuleInfo $module
}
}
43 changes: 43 additions & 0 deletions README-cOctopusServerSslCertificate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# README-cOctopusServerSslCertificate

This resource binds a SSL certificate to allow Octopus Server to listen over HTTPS.
It binds the certificate stored in the Windows Certificate store identified by the `StoreName` and `Thumbprint` to the port that the sever will listen on.
This port must match the https entry in the `WebListenPrefix` element of the cOctopusServer resource.

## Sample

First, ensure the OctopusDSC module is on your `$env:PSModulePath`. Then you can create and apply configuration like this.

```PowerShell
Configuration SampleConfig
{
Import-DscResource -Module OctopusDSC
Node "localhost"
{
cOctopusServerSslCertificate SSLCert
{
InstanceName = "OctopusServer"
Thumbprint = "<Thumbprint string from certificate>"
Ensure = "Present"
StoreName = "My"
Port = 443
}
}
}
SampleConfig
Start-DscConfiguration .\SampleConfig -Verbose -wait
Test-DscConfiguration
```

## Properties
| Property | Type | Default Value | Description |
| ------------------------------------------| ------------------------------- | -----------------| ------------------------------------------------------------------------------------------------------------------ |
| `Ensure` | `string` - `Present` or `Absent`| `Present` | The desired state of the Octopus Server - effectively whether to install or uninstall. |
| `InstanceName` | `string` | | The name of the Octopus Server instance. Use `OctopusServer` by convention unless you have more than one instance. |
| `Thumbprint` | `string` | | Thumbprint of the SSL certificate to use with Octopus Deploy, |
| `StoreName` | `string` - `My` or `WebHosting` | | Which certificate store to look for the thumbprint in. |
| `Port` | `int` | | The port to use SSL on. |
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Server authentication can be configured to use:

Other resources of note are:
* [cOctopusServerSpace](README-cOctopusServerSpace.md) to manage spaces
* [cOctopusServerSslCertificate](README-OctopusServerSslCertificate) to configure the server to use Ssl with a certificate

Version 3.0 of OctopusDSC supports Octopus Deploy 4.x and above with backwards compatibility to 3.x

Expand Down
Loading

0 comments on commit f1adca6

Please sign in to comment.