From 0993986ae5f66fb51e5242b6d28b9247b9042e2d Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Sat, 25 May 2024 10:08:57 +0100 Subject: [PATCH 1/4] #1250, #1180: resolve a threading issue with secrets, handle SecretStore better with inbuilt defaults, write docs for SecretStore --- docs/Tutorials/ImportingModules.md | 2 +- .../Routes/Utilities/RouteGrouping.md | 4 +- docs/Tutorials/Schedules.md | 32 +++--- .../Tutorials/Secrets/Examples/SecretStore.md | 84 ++++++++++++++ docs/Tutorials/Secrets/Overview.md | 45 ++++---- docs/Tutorials/Timers.md | 28 ++--- examples/server.psd1 | 9 +- examples/web-secrets-local.ps1 | 55 +++++++++ examples/web-secrets.ps1 | 36 +++--- src/Private/Secrets.ps1 | 96 ++++++++++++++-- src/Public/Secrets.ps1 | 105 +++++++++++------- 11 files changed, 372 insertions(+), 124 deletions(-) create mode 100644 docs/Tutorials/Secrets/Examples/SecretStore.md create mode 100644 examples/web-secrets-local.ps1 diff --git a/docs/Tutorials/ImportingModules.md b/docs/Tutorials/ImportingModules.md index 2545dc84a..b415724e9 100644 --- a/docs/Tutorials/ImportingModules.md +++ b/docs/Tutorials/ImportingModules.md @@ -9,7 +9,7 @@ The older [`Import-PodeModule`](../../Functions/Utilities/Import-PodeModule) and ## Modules -Modules in Pode can be imported in the normal manor using `Import-Module`. The [`Import-PodeModule`](../../Functions/Utilities/Import-PodeModule) function can now be used inside and outside of [`Start-PodeServer`](../../Functions/Core/Start-PodeServer), and should be used if you're using local modules via `ps_modules`. +Modules in Pode can be imported in the normal manner using `Import-Module`. The [`Import-PodeModule`](../../Functions/Utilities/Import-PodeModule) function can now be used inside and outside of [`Start-PodeServer`](../../Functions/Core/Start-PodeServer), and should be used if you're using local modules via `ps_modules`. ### Import via Path diff --git a/docs/Tutorials/Routes/Utilities/RouteGrouping.md b/docs/Tutorials/Routes/Utilities/RouteGrouping.md index 4306ea02f..af76594ca 100644 --- a/docs/Tutorials/Routes/Utilities/RouteGrouping.md +++ b/docs/Tutorials/Routes/Utilities/RouteGrouping.md @@ -60,7 +60,7 @@ Add-PodeRouteGroup -Path '/api' -Authentication Basic -Middleware $mid -Routes { ## Static Routes -The Groups for Static Routes work in the same manor as normal Routes, but you'll need to use [`Add-PodeStaticRouteGroup`](../../../../Functions/Routes/Add-PodeStaticRouteGroup) instead: +The Groups for Static Routes work in the same manner as normal Routes, but you'll need to use [`Add-PodeStaticRouteGroup`](../../../../Functions/Routes/Add-PodeStaticRouteGroup) instead: ```powershell Add-PodeStaticRouteGroup -Path '/assets' -Source './content/assets' -Routes { @@ -73,7 +73,7 @@ This will create 2 Static Routes at `/assets/images` and `/assets/videos`, refer ## Signal Routes -Groupings for Signal Routes also work in the same manor as normal Routes, but you'll need to use [`Add-PodeSignalRouteGroup`](../../../../Functions/Routes/Add-PodeSignalRouteGroup) instead: +Groupings for Signal Routes also work in the same manner as normal Routes, but you'll need to use [`Add-PodeSignalRouteGroup`](../../../../Functions/Routes/Add-PodeSignalRouteGroup) instead: ```powershell Add-PodeSignalRoute -Path '/ws' -Routes { diff --git a/docs/Tutorials/Schedules.md b/docs/Tutorials/Schedules.md index 861f3da06..9c754bfb1 100644 --- a/docs/Tutorials/Schedules.md +++ b/docs/Tutorials/Schedules.md @@ -37,7 +37,7 @@ Add-PodeSchedule -Name 'date' -Cron @('@minutely', '@hourly') -ScriptBlock { } ``` -Usually all schedules are created within the main `Start-PodeServer` scope, however it is possible to create adhoc schedules with routes/etc. If you create adhoc schedules in this manor, you might notice that they don't run; this is because the Runspace that schedules use to run won't have been configured. You can configure by using `-EnablePool` on [`Start-PodeServer`](../../Functions/Core/Start-PodeServer): +Usually all schedules are created within the main `Start-PodeServer` scope, however it is possible to create adhoc schedules with routes/etc. If you create adhoc schedules in this manner, you might notice that they don't run; this is because the Runspace that schedules use to run won't have been configured. You can configure by using `-EnablePool` on [`Start-PodeServer`](../../Functions/Core/Start-PodeServer): ```powershell Start-PodeServer -EnablePool Schedules { @@ -169,18 +169,18 @@ Invoke-PodeSchedule -Name 'date' -ArgumentList @{ Date = [DateTime]::Now } The following is the structure of the Schedule object internally, as well as the object that is returned from [`Get-PodeSchedule`](../../Functions/Schedules/Get-PodeSchedule): -| Name | Type | Description | -| ---- | ---- | ----------- | -| Name | string | The name of the Schedule | -| StartTime | datetime | The delayed start time of the Schedule | -| EndTime | datetime | The end time of the Schedule | -| Crons | hashtable[] | The cron expressions of the Schedule, but parsed into an internal format | -| CronsRaw | string[] | The raw cron expressions that were supplied | -| Limit | int | The number of times the Schedule should run - 0 if running infinitely | -| Count | int | The number of times the Schedule has run | -| LastTriggerTime | datetime | The datetime the Schedule was last triggered | -| NextTriggerTime | datetime | The datetime the Schedule will next be triggered | -| Script | scriptblock | The scriptblock of the Schedule | -| Arguments | hashtable | The arguments supplied from ArgumentList | -| OnStart | bool | Should the Schedule run once when the server is starting, or once the server has fully loaded | -| Completed | bool | Has the Schedule completed all of its runs | +| Name | Type | Description | +| --------------- | ----------- | --------------------------------------------------------------------------------------------- | +| Name | string | The name of the Schedule | +| StartTime | datetime | The delayed start time of the Schedule | +| EndTime | datetime | The end time of the Schedule | +| Crons | hashtable[] | The cron expressions of the Schedule, but parsed into an internal format | +| CronsRaw | string[] | The raw cron expressions that were supplied | +| Limit | int | The number of times the Schedule should run - 0 if running infinitely | +| Count | int | The number of times the Schedule has run | +| LastTriggerTime | datetime | The datetime the Schedule was last triggered | +| NextTriggerTime | datetime | The datetime the Schedule will next be triggered | +| Script | scriptblock | The scriptblock of the Schedule | +| Arguments | hashtable | The arguments supplied from ArgumentList | +| OnStart | bool | Should the Schedule run once when the server is starting, or once the server has fully loaded | +| Completed | bool | Has the Schedule completed all of its runs | diff --git a/docs/Tutorials/Secrets/Examples/SecretStore.md b/docs/Tutorials/Secrets/Examples/SecretStore.md new file mode 100644 index 000000000..81ced1d39 --- /dev/null +++ b/docs/Tutorials/Secrets/Examples/SecretStore.md @@ -0,0 +1,84 @@ +# SecretStore + +This page gives some brief details, and an example, of how to use the `Microsoft.PowerShell.SecretStore` PowerShell module secret vault provider. + +This is a locally stored secret vault. + +## Install + +Before using the `Microsoft.PowerShell.SecretStore` provider, you will first need to install the module: + +```powershell +Install-Module Microsoft.PowerShell.SecretManagement, Microsoft.PowerShell.SecretStore +``` + +## Register + +When registering a new secret vault using `Microsoft.PowerShell.SecretStore`, via [`Register-PodeSecretVault`], the `-UnlockSecret` parameter is **mandatory**. This will be used to assign the required default password for the secret vault and to periodically unlock the vault. + +There are also some default values set for some parameters to make life a little easier, however, these can be overwritten if needed by directly supplying the parameter: + +| Parameter | Default | Description | +| ------------------------------------ | ---------- | --------------------------------------------------------------------------------------------------------------------------------- | +| `UnlockInterval` | 1 minute | Used to assign an unlock period, as well as the PasswordTimeout to auto-lock the vault | +| `VaultParameters['Authentication']` | Password | Used to tell the vault to to be locked/unlocked | +| `VaultParameters['Interaction']` | None | Used to tell the vault where it should be interactive or not | +| `VaultParameters['PasswordTimeout']` | 70 seconds | Used to auto-lock the vault after being unlocked. The vault if not supplied is based on the `-UnlockInterval` values + 10 seconds | + +For example: + +```powershell +Register-PodeSecretVault ` + -Name 'ExampleVault' ` + -ModuleName 'Microsoft.PowerShell.SecretStore' ` + -UnlockSecret 'Sup3rSecur3Pa$$word!' +``` + +## Secret Management + +Creating, updating, and using secrets are all done in the usual manner, as outlined in the [Overview](../../Overview): + +```powershell +# set a secret in the local vault +Set-PodeSecret -Key 'example' -Vault 'ExampleVault' -InputObject 'hello, world!' + +# mount the "example" secret from local vault, making it accessible via $secret:example +Mount-PodeSecret -Name 'example' -Vault 'ExampleVault' -Key 'example' + +# use the secret in a route +Add-PodeRoute -Method Get -Path '/' -ScriptBlock { + Write-PodeJsonResponse @{ Value = $secret:example } +} +``` + +## Example + +The following is a smaller example of the example [found here](https://github.com/Badgerati/Pode/blob/develop/examples/web-secrets-local.ps1) and shows how to set up a server to register a local SecretStore vault, manage a secret within that vault, and return it via a Route. + +```powershell +Start-PodeServer -Threads 2 { + Add-PodeEndpoint -Address * -Port 8080 -Protocol Http + + # register the vault + Register-PodeSecretVault ` + -Name 'ExampleVault' ` + -ModuleName 'Microsoft.PowerShell.SecretStore' ` + -UnlockSecret 'Sup3rSecur3Pa$$word!' + + # set a secret in the local vault + Set-PodeSecret -Key 'example' -Vault 'ExampleVault' -InputObject 'hello, world!' + + # mount the "example" secret from local vault, making it accessible via $secret:example + Mount-PodeSecret -Name 'example' -Vault 'ExampleVault' -Key 'example' + + # retrieve the secret in a route + Add-PodeRoute -Method Get -Path '/' -ScriptBlock { + Write-PodeJsonResponse @{ Value = $secret:example } + } + + # update the secret in a route + Add-PodeRoute -Method Post -Path '/' -ScriptBlock { + $secret:example = $WebEvent.Data.Value + } +} +``` diff --git a/docs/Tutorials/Secrets/Overview.md b/docs/Tutorials/Secrets/Overview.md index 2aa9ae7d5..a9dbadbb0 100644 --- a/docs/Tutorials/Secrets/Overview.md +++ b/docs/Tutorials/Secrets/Overview.md @@ -2,13 +2,13 @@ You can register and mount secret values from secret vaults, like Azure KeyVault or HashiCorp Vault, into Pode for use in Routes, Middleware, etc. -Secrets can also be referenced from a vault in an adhoc manor, without needing to mount them first. You can also create, update and remove secrets in vaults. +Secrets can also be referenced from a vault in an adhoc manner, without needing to mount them first. You can also create, update, and remove secrets in vaults. -The values of mounted secrets may also be cached for a period of time, to reduce load on the vault. +The values of mounted secrets may also be cached for a period of time, to reduce load on the vault as well as speed up lookups. ## Registering -In order to reference Secrets from a vault you first need to register that vault using [`Register-PodeSecretVault`](../../../Functions/Secrets/Register-PodeSecretVault). Registering a vault registers the vault within Pode, but will also call any logic needed by the registration type being used. For example, if using Secret Management then Pode will call `Register-SecretVault` for you. +To reference Secrets from a vault you first need to register that vault using [`Register-PodeSecretVault`](../../../Functions/Secrets/Register-PodeSecretVault). Registering a vault registers the vault within Pode, but will also call any logic needed by the registration type being used. For example, if using Secret Management then Pode will call `Register-SecretVault` for you. A further example, as follows, will use the Secret Management PowerShell module to register an Azure KeyVault within Pode: @@ -21,23 +21,30 @@ Register-PodeSecretVault -Name 'FriendlyVaultName' -ModuleName 'Az.KeyVault' -Va You can find more information on the [Secret Management](../Types/SecretManagement) page. The general parameters for all types are: -| Parameter | Description | -| --------- | ----------- | -| Name | This is a friendly name for the vault within Pode that you can reference | +| Parameter | Description | +| --------------- | -------------------------------------------------------------------------------- | +| Name | This is a friendly name for the vault within Pode that you can reference | | VaultParameters | This is a hashtable of options, for the vault, that is supplied to vault scripts | !!! note - You can unregister a vault via [`Unregister-PodeSecretVault`](../../../Functions/Secrets/Unregister-PodeSecretVault). All vaults are automatically unregistered at when the server stops - unless it was auto-imported. + You can unregister a vault via [`Unregister-PodeSecretVault`](../../../Functions/Secrets/Unregister-PodeSecretVault). All vaults are automatically unregistered when the server stops - unless it was auto-imported. ### Types -At present there are just two registration types implemented for registering secret vaults: +At present, there are just two registration types implemented for registering secret vaults: -* [Secret Management](../Types/SecretManagement) (powershell module) +* [Secret Management](../Types/SecretManagement) (PowerShell module) * [Custom](../Types/Custom) !!! tip - For the SecretManagement module you can read a "getting started" [guide for it here](https://learn.microsoft.com/en-us/powershell/utility-modules/secretmanagement/how-to/using-secrets-in-automation?view=ps-modules) when using the module in automated scenarios. + For the SecretManagement module you can read a "getting started" [guide here](https://learn.microsoft.com/en-us/powershell/utility-modules/secretmanagement/how-to/using-secrets-in-automation?view=ps-modules) when using the module in automated scenarios. + +### Vault Examples + +You can find some quick examples for some vault providers below: + +* [SecretStore](../Examples/SecretStore) + ### Initialise @@ -59,11 +66,11 @@ Register-PodeSecretVault -Name 'VaultName' -ModuleName 'Az.KeyVault' ` ### Auto-Import -Similar to modules and functions, Pode will auto-import any secret vaults registered outside of vault. You can find more [information here](../../Scoping#secret-vaults) +Similar to modules and functions, Pode will auto-import any secret vaults registered outside of Pode. You can find more [information here](../../Scoping#secret-vaults) ### Unlock -Some vaults require unlocking first, or an authorization token to be be acquired to access the vault. Unlocking applies to all registration types, and to configure unlocking for use you'll first need to supply either an `-UnlockSecret` or an `-UnlockSecureSecret` to [`Register-PodeSecretVault`](../../../Functions/Secrets/Register-PodeSecretVault). +Some vaults require unlocking first, or an authorization token to be acquired to access the vault. Unlocking applies to all registration types, and to configure unlocking for use you'll first need to supply either an `-UnlockSecret` or an `-UnlockSecureSecret` to [`Register-PodeSecretVault`](../../../Functions/Secrets/Register-PodeSecretVault). !!! important If you're using a custom registration type, you'll also need to supply an `-UnlockScriptBlock`. @@ -86,7 +93,7 @@ Pode will automatically call the unlock logic after registration, but you can st Unlock-PodeSecretVault -Name 'VaultName' ``` -If you need to periodically check/unlock your vault, then Pode can do this automatically for you. To achieve this you can supply a number of minutes for the `-UnlockInterval` parameter on [`Register-PodeSecretVault`](../../../Functions/Secrets/Register-PodeSecretVault), this will tell Pode to automatically check/unlock the vault after the first unlock as occurred. +If you need to periodically check/unlock your vault, then Pode can do this automatically for you. To achieve this you can supply a number of minutes for the `-UnlockInterval` parameter on [`Register-PodeSecretVault`](../../../Functions/Secrets/Register-PodeSecretVault), this will tell Pode to automatically check/unlock the vault after the first unlock has occurred. ## Mounting @@ -98,7 +105,7 @@ Mount-PodeSecret -Name 'SecretName' -Vault 'VaultName' -Key 'SecretKeyNameInVaul The `-Name` is the name of the secret you'll be using to reference the secret throughout Pode. The `-Vault` parameter is the name of the vault from [`Register-PodeSecretVault`](../../../Functions/Secrets/Register-PodeSecretVault), and the `-Key` is the path/name of the secret within the vault itself. -Some secrets will be returned as hashtables - such as from HashiCorp Vault. In some cases you might only want certain properties to be returned from this secret, and you can limit the properties returned by using the `-Property` parameter. For example, if a secret has 5 keys named key1 to key5, you can limit this to just key2 and key4: +Some secrets will be returned as hashtables - such as from HashiCorp Vault. In some cases, you might only want certain properties to be returned from this secret, and you can limit the properties returned by using the `-Property` parameter. For example, if a secret has 5 keys named key1 to key5, you can limit this to just key2 and key4: ```powershell Mount-PodeSecret -Name 'SecretName' -Vault 'VaultName' -Key 'SecretKeyNameInVault' -Property key2, key4 @@ -149,11 +156,11 @@ Add-PodeRoute -Method Post -Path '/secret' -ScriptBlock { ### Caching !!! important - The cache is an in-memory in-application cache, and is unencrypted. It is never stored to disk, and cached values are wiped once their expiry is up; the cache is also wiped when the server is stopped. + The cache is an in-memory in-application cache, and is unencrypted. It is never stored on disk, and cached values are wiped once their expiry is up; the cache is also wiped when the server is stopped. To reduce round-trip time by constantly going to a vault, as well as to reduce stress on a vault, you can optionally enable caching on secrets - the cache by default is disabled. -You can either supply a `-CacheTtl` as a number of minutes to [`Register-PodeSecretVault`](../../../Functions/Secrets/Register-PodeSecretVault), and all secrets mounted will be cached. Or you can supply a `-CacheTtl` only to specific mounted secrets - you can also use this option to disable caching for a secret, by supplying a value of 0, even if the vault itself is registered with a cache TTL. +You can either supply a `-CacheTtl` as a number of minutes to [`Register-PodeSecretVault`](../../../Functions/Secrets/Register-PodeSecretVault) and all secrets mounted will be cached. Or you can supply a `-CacheTtl` only to specific mounted secrets - you can also use this option to disable caching for a secret, by supplying a value of 0, even if the vault itself is registered with a cache TTL. To enable caching for all mounted secrets from a vault - but with one disabled, and one overriding: @@ -192,11 +199,11 @@ Mount-PodeSecret -Name 'SecretName2' -Vault 'VaultName' -Key 'SecretKeyNameInVau ## Adhoc -There is also support for creating/updating, retrieving, and removing secrets in an adhoc manor from registered vaults - without having to mount them. +There is also support for creating/updating, retrieving, and removing secrets in an adhoc manner from registered vaults - without having to mount them. ### Create -To create a new secret, and well as update an existing ones value, you can use [`Set-PodeSecret`](../../../Functions/Secrets/Set-PodeSecret): +To create a new secret, as well as update an existing value, you can use [`Set-PodeSecret`](../../../Functions/Secrets/Set-PodeSecret): ```powershell Add-PodeRoute -Method Post -Path '/adhoc/:key' -ScriptBlock { @@ -220,7 +227,7 @@ Add-PodeRoute -Method Get -Path '/adhoc/:key' -ScriptBlock { ### Remove -And to then remove the secret from the vault you can use [`Remove-PodeSecret`](../../../Functions/Secrets/Remove-PodeSecret): +To remove the secret from the vault you can use [`Remove-PodeSecret`](../../../Functions/Secrets/Remove-PodeSecret): ```powershell Add-PodeRoute -Method Delete -Path '/adhoc/:key' -ScriptBlock { diff --git a/docs/Tutorials/Timers.md b/docs/Tutorials/Timers.md index 221c23fdf..df3a70062 100644 --- a/docs/Tutorials/Timers.md +++ b/docs/Tutorials/Timers.md @@ -15,7 +15,7 @@ Add-PodeTimer -Name 'date' -Interval 5 -ScriptBlock { } ``` -Usually all timers are created within the main `Start-PodeServer` scope, however it is possible to create adhoc timers with routes/etc. If you create adhoc timers in this manor, you might notice that they don't run; this is because the Runspace that timers use to run won't have been configured. You can configure by using `-EnablePool` on [`Start-PodeServer`](../../Functions/Core/Start-PodeServer): +Usually all timers are created within the main `Start-PodeServer` scope, however it is possible to create adhoc timers with routes/etc. If you create adhoc timers in this manner, you might notice that they don't run; this is because the Runspace that timers use to run won't have been configured. You can configure by using `-EnablePool` on [`Start-PodeServer`](../../Functions/Core/Start-PodeServer): ```powershell Start-PodeServer -EnablePool Timers { @@ -136,16 +136,16 @@ Invoke-PodeTimer -Name 'date' -ArgumentList 'Arg1', 'Arg2' The following is the structure of the Timer object internally, as well as the object that is returned from [`Get-PodeTimer`](../../Functions/Timers/Get-PodeTimer): -| Name | Type | Description | -| ---- | ---- | ----------- | -| Name | string | The name of the Timer | -| Interval | int | How often the Timer runs, defined in seconds | -| Limit | int | The number of times the Timer should run - 0 if running forever | -| Skip | int | The number of times the Timer should skip being triggered | -| Count | int | The number of times the Timer has run | -| LastTriggerTime | datetime | The datetime the Timer was last triggered | -| NextTriggerTime | datetime | The datetime the Timer will next be triggered | -| Script | scriptblock | The scriptblock of the Timer | -| Arguments | object[] | The arguments supplied from ArgumentList | -| OnStart | bool | Should the Timer run once when the server is starting, or once the server has fully loaded | -| Completed | bool | Has the Timer completed all of its runs | +| Name | Type | Description | +| --------------- | ----------- | ------------------------------------------------------------------------------------------ | +| Name | string | The name of the Timer | +| Interval | int | How often the Timer runs, defined in seconds | +| Limit | int | The number of times the Timer should run - 0 if running forever | +| Skip | int | The number of times the Timer should skip being triggered | +| Count | int | The number of times the Timer has run | +| LastTriggerTime | datetime | The datetime the Timer was last triggered | +| NextTriggerTime | datetime | The datetime the Timer will next be triggered | +| Script | scriptblock | The scriptblock of the Timer | +| Arguments | object[] | The arguments supplied from ArgumentList | +| OnStart | bool | Should the Timer run once when the server is starting, or once the server has fully loaded | +| Completed | bool | Has the Timer completed all of its runs | diff --git a/examples/server.psd1 b/examples/server.psd1 index 99191fb0f..4d9b598b8 100644 --- a/examples/server.psd1 +++ b/examples/server.psd1 @@ -40,12 +40,17 @@ } } AutoImport = @{ - Functions = @{ + Functions = @{ ExportOnly = $true } - Modules = @{ + Modules = @{ ExportOnly = $true } + SecretVaults = @{ + SecretManagement = @{ + ExportOnly = $true + } + } } Request = @{ Timeout = 30 diff --git a/examples/web-secrets-local.ps1 b/examples/web-secrets-local.ps1 new file mode 100644 index 000000000..259dfb9e8 --- /dev/null +++ b/examples/web-secrets-local.ps1 @@ -0,0 +1,55 @@ +$path = Split-Path -Parent -Path (Split-Path -Parent -Path $MyInvocation.MyCommand.Path) +Import-Module "$($path)/src/Pode.psm1" -Force -ErrorAction Stop + +# or just: +# Import-Module Pode + +# make sure to install the Microsoft.PowerShell.SecretStore modules! +# Install-Module Microsoft.PowerShell.SecretManagement, Microsoft.PowerShell.SecretStore + +Start-PodeServer -Threads 2 { + # listen + Add-PodeEndpoint -Address * -Port 8080 -Protocol Http + + # logging + New-PodeLoggingMethod -Terminal | Enable-PodeErrorLogging + + + # secret manage local vault + $params = @{ + Name = 'PodeTest_LocalVault' + ModuleName = 'Microsoft.PowerShell.SecretStore' + UnlockSecret = 'Sup3rSecur3Pa$$word!' + } + + Register-PodeSecretVault @params + + + # set a secret in the local vault + Set-PodeSecret -Key 'hello' -Vault 'PodeTest_LocalVault' -InputObject 'world' + + + # mount a secret from local vault + Mount-PodeSecret -Name 'hello' -Vault 'PodeTest_LocalVault' -Key 'hello' + + + # routes to get/update secret in local vault + Add-PodeRoute -Method Get -Path '/module' -ScriptBlock { + Write-PodeJsonResponse @{ Value = $secret:hello } + } + + Add-PodeRoute -Method Post -Path '/module' -ScriptBlock { + $secret:hello = $WebEvent.Data.Value + } + + + Add-PodeRoute -Method Post -Path '/adhoc/:key' -ScriptBlock { + Set-PodeSecret -Key $WebEvent.Parameters['key'] -Vault 'PodeTest_LocalVault' -InputObject $WebEvent.Data['value'] + Mount-PodeSecret -Name $WebEvent.Data['name'] -Vault 'PodeTest_LocalVault' -Key $WebEvent.Parameters['key'] + } + + Add-PodeRoute -Method Delete -Path '/adhoc/:key' -ScriptBlock { + Remove-PodeSecret -Key $WebEvent.Parameters['key'] -Vault 'PodeTest_LocalVault' + Dismount-PodeSecret -Name $WebEvent.Parameters['key'] + } +} diff --git a/examples/web-secrets.ps1 b/examples/web-secrets.ps1 index efb36a890..13c16d680 100644 --- a/examples/web-secrets.ps1 +++ b/examples/web-secrets.ps1 @@ -1,5 +1,5 @@ param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [string] $AzureSubscriptionId ) @@ -12,7 +12,7 @@ Import-Module "$($path)/src/Pode.psm1" -Force -ErrorAction Stop Start-PodeServer -Threads 2 { # listen - Add-PodeEndpoint -Address * -Port 8085 -Protocol Http + Add-PodeEndpoint -Address * -Port 8080 -Protocol Http # logging New-PodeLoggingMethod -Terminal | Enable-PodeErrorLogging @@ -20,27 +20,23 @@ Start-PodeServer -Threads 2 { # secret manage azure keyvault - need to run "Connect-AzAccount" first! Register-PodeSecretVault -Name 'PodeTest_SMAZVault' -ModuleName 'Az.KeyVault' -VaultParameters @{ - AZKVaultName = 'pode-test-kv' + AZKVaultName = 'pode-test-kv' SubscriptionId = $AzureSubscriptionId } # custom vault cli - Register-PodeSecretVault -Name 'PodeTest_CustomVault' -CacheTtl 1 ` - -VaultParameters @{ - Address = 'http://127.0.0.1:8200' - } ` - -ScriptBlock { - param($config, $key) - return (vault kv get -format json -address $config.Address -mount secret $key | ConvertFrom-Json -AsHashtable).data.data - } ` - -SetScriptBlock { - param($config, $key, $value) - vault kv put -address $config.Address -mount secret $key "$($value.Keys[0])=$($value.Values[0])" - } ` - -RemoveScriptBlock { - param($config, $key) - vault kv destroy -address $config.Address -versions 1 -mount secret $key - } + Register-PodeSecretVault -Name 'PodeTest_CustomVault' -CacheTtl 1 -VaultParameters @{ + Address = 'http://127.0.0.1:8200' + } -ScriptBlock { + param($config, $key) + return (vault kv get -format json -address $config.Address -mount secret $key | ConvertFrom-Json -AsHashtable).data.data + } -SetScriptBlock { + param($config, $key, $value) + vault kv put -address $config.Address -mount secret $key "$($value.Keys[0])=$($value.Values[0])" + } -RemoveScriptBlock { + param($config, $key) + vault kv destroy -address $config.Address -versions 1 -mount secret $key + } # mount a secret from vault cli @@ -92,6 +88,6 @@ Start-PodeServer -Threads 2 { Add-PodeRoute -Method Delete -Path '/adhoc/:key' -ScriptBlock { Remove-PodeSecret -Key $WebEvent.Parameters['key'] -Vault 'PodeTest_CustomVault' - Dismount-PodeSecret -Name $WebEvent.Parameters['key'] + Dismount-PodeSecret -Name $WebEvent.Parameters['key'] } } diff --git a/src/Private/Secrets.ps1 b/src/Private/Secrets.ps1 index 612564fa2..744ddd31c 100644 --- a/src/Private/Secrets.ps1 +++ b/src/Private/Secrets.ps1 @@ -36,14 +36,84 @@ function Register-PodeSecretManagementVault { $null = Import-Module -Name Microsoft.PowerShell.SecretManagement -Force -DisableNameChecking -Scope Global -ErrorAction Stop -Verbose:$false $null = Import-Module -Name $ModuleName -Force -DisableNameChecking -Scope Global -ErrorAction Stop -Verbose:$false + # export the modules for pode + Export-PodeModule -Name @('Microsoft.PowerShell.SecretManagement', $ModuleName) + + # is this the local SecretStore provider? + $isSecretStore = ($ModuleName -ieq 'Microsoft.PowerShell.SecretStore') + + # check if we have an unlock password for local secret store + if ($isSecretStore) { + if ([string]::IsNullOrEmpty($VaultConfig.Unlock.Secret)) { + throw 'An "-UnlockSecret" is required when using Microsoft.PowerShell.SecretStore' + } + } + + # does the local secret store already exist? + $secretStoreExists = ($isSecretStore -and (Test-PodeSecretVaultInternal -Name $VaultName)) + + # do we have vault params? + $hasVaultParams = ($null -ne $VaultConfig.Parameters) + # attempt to register the vault - $null = Register-SecretVault -Name $VaultName -ModuleName $ModuleName -VaultParameters $VaultConfig.Parameters -Confirm:$false -AllowClobber -ErrorAction Stop + $registerParams = @{ + Name = $VaultName + ModuleName = $ModuleName + Confirm = $false + AllowClobber = $true + ErrorAction = 'Stop' + } + + if (!$isSecretStore -and $hasVaultParams) { + $registerParams['VaultParameters'] = $VaultConfig.Parameters + } + + $null = Register-SecretVault @registerParams # all is good, so set the config $VaultConfig['SecretManagement'] = @{ VaultName = $VaultName ModuleName = $ModuleName } + + # set local secret store config + if ($isSecretStore) { + if (!$hasVaultParams) { + $VaultConfig.Parameters = @{} + } + + $vaultParams = $VaultConfig.Parameters + + # remove the password + $vaultParams.Remove('Password') + + # set default authentication and interaction flags + if ([string]::IsNullOrEmpty($vaultParams.Authentication)) { + $vaultParams['Authentication'] = 'Password' + } + + if ([string]::IsNullOrEmpty($vaultParams.Interaction)) { + $vaultParams['Interaction'] = 'None' + } + + # set default password timeout and unlock interval to 1 minute + if ($VaultConfig.Unlock.Interval -le 0) { + $VaultConfig.Unlock.Interval = 1 + } + + # unlock the vault, and set password + $VaultConfig | Unlock-PodeSecretManagementVault + + # set the password timeout for the vault + if (!$secretStoreExists) { + if ($VaultConfig.Parameters.PasswordTimeout -le 0) { + $vaultParams['PasswordTimeout'] = ($VaultConfig.Unlock.Interval * 60) + 10 + } + } + + # set config + $null = Set-SecretStoreConfiguration @vaultParams -Confirm:$false -ErrorAction Stop + } } function Register-PodeSecretCustomVault { @@ -129,10 +199,10 @@ function Unlock-PodeSecretCustomVault { } # unlock the vault, and get back an expiry - $expiry = (Invoke-PodeScriptBlock -ScriptBlock $VaultConfig.Custom.Unlock -Splat -Return -Arguments @( - $VaultConfig.Parameters, + $expiry = Invoke-PodeScriptBlock -ScriptBlock $VaultConfig.Custom.Unlock -Splat -Return -Arguments @( + $VaultConfig.Parameters, (ConvertFrom-SecureString -SecureString $VaultConfig.Unlock.Secret -AsPlainText) - )) + ) # return expiry if given, otherwise check interval if ($null -ne $expiry) { @@ -222,10 +292,10 @@ function Get-PodeSecretCustomKey { $_vault = $PodeContext.Server.Secrets.Vaults[$Vault] # fetch the secret - return (Invoke-PodeScriptBlock -ScriptBlock $_vault.Custom.Read -Splat -Return -Arguments (@( - $_vault.Parameters, - $Key - ) + $ArgumentList)) + return Invoke-PodeScriptBlock -ScriptBlock $_vault.Custom.Read -Splat -Return -Arguments (@( + $_vault.Parameters, + $Key + ) + $ArgumentList) } function Set-PodeSecretManagementKey { @@ -439,4 +509,14 @@ function Protect-PodeSecretValueType { } return $Value +} + +function Test-PodeSecretVaultInternal { + param( + [Parameter(Mandatory = $true)] + [string] + $Name + ) + + return ($null -ne (Get-SecretVault -Name $Name -ErrorAction Ignore)) } \ No newline at end of file diff --git a/src/Public/Secrets.ps1 b/src/Public/Secrets.ps1 index ca7eb47c5..82360ab50 100644 --- a/src/Public/Secrets.ps1 +++ b/src/Public/Secrets.ps1 @@ -144,6 +144,7 @@ function Register-PodeSecretVault { Type = $PSCmdlet.ParameterSetName.ToLowerInvariant() Parameters = $VaultParameters AutoImported = $false + LockableName = "__Pode_SecretVault_$($Name)__" Unlock = @{ Secret = $UnlockSecureSecret Expiry = $null @@ -182,6 +183,9 @@ function Register-PodeSecretVault { # create timer to clear cached secrets every minute Start-PodeSecretCacheHousekeeper + # create a lockable so secrets are thread safe + New-PodeLockable -Name $vault.LockableName + # add vault config to context $PodeContext.Server.Secrets.Vaults[$Name] = $vault @@ -270,13 +274,15 @@ function Unlock-PodeSecretVault { } # unlock depending on vault type, and set expiry - switch ($vault.Type) { - 'custom' { - $expiry = $vault | Unlock-PodeSecretCustomVault - } - - 'secretmanagement' { - $expiry = $vault | Unlock-PodeSecretManagementVault + $expiry = Lock-PodeObject -Name $vault.LockableName -Return -ScriptBlock { + switch ($vault.Type) { + 'custom' { + return ($vault | Unlock-PodeSecretCustomVault) + } + + 'secretmanagement' { + return ($vault | Unlock-PodeSecretManagementVault) + } } } @@ -556,13 +562,16 @@ function Get-PodeSecret { } # fetch the secret depending on vault type - switch ($PodeContext.Server.Secrets.Vaults[$secret.Vault].Type) { - 'custom' { - $value = Get-PodeSecretCustomKey -Vault $secret.Vault -Key $secret.Key -ArgumentList $secret.Arguments - } - - 'secretmanagement' { - $value = Get-PodeSecretManagementKey -Vault $secret.Vault -Key $secret.Key + $vault = $PodeContext.Server.Secrets.Vaults[$secret.Vault] + $value = Lock-PodeObject -Name $vault.LockableName -Return -ScriptBlock { + switch ($vault.Type) { + 'custom' { + return Get-PodeSecretCustomKey -Vault $secret.Vault -Key $secret.Key -ArgumentList $secret.Arguments + } + + 'secretmanagement' { + return Get-PodeSecretManagementKey -Vault $secret.Vault -Key $secret.Key + } } } @@ -678,13 +687,16 @@ function Update-PodeSecret { } # set the secret depending on vault type - switch ($PodeContext.Server.Secrets.Vaults[$secret.Vault].Type) { - 'custom' { - Set-PodeSecretCustomKey -Vault $secret.Vault -Key $secret.Key -Value $InputObject -Metadata $Metadata -ArgumentList $secret.Arguments - } - - 'secretmanagement' { - Set-PodeSecretManagementKey -Vault $secret.Vault -Key $secret.Key -Value $InputObject -Metadata $Metadata + $vault = $PodeContext.Server.Secrets.Vaults[$secret.Vault] + Lock-PodeObject -Name $vault.LockableName -ScriptBlock { + switch ($vault.Type) { + 'custom' { + Set-PodeSecretCustomKey -Vault $secret.Vault -Key $secret.Key -Value $InputObject -Metadata $Metadata -ArgumentList $secret.Arguments + } + + 'secretmanagement' { + Set-PodeSecretManagementKey -Vault $secret.Vault -Key $secret.Key -Value $InputObject -Metadata $Metadata + } } } } @@ -730,13 +742,16 @@ function Remove-PodeSecret { } # remove the secret depending on vault type - switch ($PodeContext.Server.Secrets.Vaults[$Vault].Type) { - 'custom' { - Remove-PodeSecretCustomKey -Vault $Vault -Key $Key -ArgumentList $ArgumentList - } - - 'secretmanagement' { - Remove-PodeSecretManagementKey -Vault $Vault -Key $Key + $_vault = $PodeContext.Server.Secrets.Vaults[$Vault] + Lock-PodeObject -Name $_vault.LockableName -ScriptBlock { + switch ($_vault.Type) { + 'custom' { + Remove-PodeSecretCustomKey -Vault $Vault -Key $Key -ArgumentList $ArgumentList + } + + 'secretmanagement' { + Remove-PodeSecretManagementKey -Vault $Vault -Key $Key + } } } } @@ -799,13 +814,16 @@ function Read-PodeSecret { } # fetch the secret depending on vault type - switch ($PodeContext.Server.Secrets.Vaults[$Vault].Type) { - 'custom' { - $value = Get-PodeSecretCustomKey -Vault $Vault -Key $Key -ArgumentList $ArgumentList - } - - 'secretmanagement' { - $value = Get-PodeSecretManagementKey -Vault $Vault -Key $Key + $_vault = $PodeContext.Server.Secrets.Vaults[$Vault] + $value = Lock-PodeObject -Name $_vault.LockableName -Return -ScriptBlock { + switch ($_vault.Type) { + 'custom' { + return Get-PodeSecretCustomKey -Vault $Vault -Key $Key -ArgumentList $ArgumentList + } + + 'secretmanagement' { + return Get-PodeSecretManagementKey -Vault $Vault -Key $Key + } } } @@ -884,13 +902,16 @@ function Set-PodeSecret { $InputObject = Protect-PodeSecretValueType -Value $InputObject # set the secret depending on vault type - switch ($PodeContext.Server.Secrets.Vaults[$Vault].Type) { - 'custom' { - Set-PodeSecretCustomKey -Vault $Vault -Key $Key -Value $InputObject -Metadata $Metadata -ArgumentList $ArgumentList - } - - 'secretmanagement' { - Set-PodeSecretManagementKey -Vault $Vault -Key $Key -Value $InputObject -Metadata $Metadata + $_vault = $PodeContext.Server.Secrets.Vaults[$Vault] + Lock-PodeObject -Name $_vault.LockableName -ScriptBlock { + switch ($_vault.Type) { + 'custom' { + Set-PodeSecretCustomKey -Vault $Vault -Key $Key -Value $InputObject -Metadata $Metadata -ArgumentList $ArgumentList + } + + 'secretmanagement' { + Set-PodeSecretManagementKey -Vault $Vault -Key $Key -Value $InputObject -Metadata $Metadata + } } } } \ No newline at end of file From 1462d37960e338b7ea560b69d1b75f00a8c4aa1e Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Sun, 26 May 2024 12:12:47 +0100 Subject: [PATCH 2/4] #1250, #1180: minor doc tweaks, and unique-ify export lists --- docs/Tutorials/Secrets/Examples/SecretStore.md | 12 ++++++------ src/Public/AutoImport.ps1 | 4 ++++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/Tutorials/Secrets/Examples/SecretStore.md b/docs/Tutorials/Secrets/Examples/SecretStore.md index 81ced1d39..4a2e25dee 100644 --- a/docs/Tutorials/Secrets/Examples/SecretStore.md +++ b/docs/Tutorials/Secrets/Examples/SecretStore.md @@ -18,12 +18,12 @@ When registering a new secret vault using `Microsoft.PowerShell.SecretStore`, vi There are also some default values set for some parameters to make life a little easier, however, these can be overwritten if needed by directly supplying the parameter: -| Parameter | Default | Description | -| ------------------------------------ | ---------- | --------------------------------------------------------------------------------------------------------------------------------- | -| `UnlockInterval` | 1 minute | Used to assign an unlock period, as well as the PasswordTimeout to auto-lock the vault | -| `VaultParameters['Authentication']` | Password | Used to tell the vault to to be locked/unlocked | -| `VaultParameters['Interaction']` | None | Used to tell the vault where it should be interactive or not | -| `VaultParameters['PasswordTimeout']` | 70 seconds | Used to auto-lock the vault after being unlocked. The vault if not supplied is based on the `-UnlockInterval` values + 10 seconds | +| Parameter | Default | Description | +| ------------------------------------ | ---------- | -------------------------------------------------------------------------------------------------------------------------------- | +| `UnlockInterval` | 1 minute | Used to assign an unlock period, as well as the PasswordTimeout to auto-lock the vault | +| `VaultParameters['Authentication']` | Password | Used to tell the vault to to be locked/unlocked | +| `VaultParameters['Interaction']` | None | Used to tell the vault where it should be interactive or not | +| `VaultParameters['PasswordTimeout']` | 70 seconds | Used to auto-lock the vault after being unlocked. The value if not supplied is based on the `-UnlockInterval` value + 10 seconds | For example: diff --git a/src/Public/AutoImport.ps1 b/src/Public/AutoImport.ps1 index a02005c60..4421700c1 100644 --- a/src/Public/AutoImport.ps1 +++ b/src/Public/AutoImport.ps1 @@ -20,6 +20,7 @@ function Export-PodeModule { ) $PodeContext.Server.AutoImport.Modules.ExportList += @($Name) + $PodeContext.Server.AutoImport.Modules.ExportList = $PodeContext.Server.AutoImport.Modules.ExportList | Sort-Object -Unique } <# @@ -49,6 +50,7 @@ function Export-PodeSnapin { } $PodeContext.Server.AutoImport.Snapins.ExportList += @($Name) + $PodeContext.Server.AutoImport.Snapins.ExportList = $PodeContext.Server.AutoImport.Snapins.ExportList | Sort-Object -Unique } <# @@ -73,6 +75,7 @@ function Export-PodeFunction { ) $PodeContext.Server.AutoImport.Functions.ExportList += @($Name) + $PodeContext.Server.AutoImport.Functions.ExportList = $PodeContext.Server.AutoImport.Functions.ExportList | Sort-Object -Unique } <# @@ -105,4 +108,5 @@ function Export-PodeSecretVault { ) $PodeContext.Server.AutoImport.SecretVaults[$Type].ExportList += @($Name) + $PodeContext.Server.AutoImport.SecretVaults[$Type].ExportList = $PodeContext.Server.AutoImport.SecretVaults[$Type].ExportList | Sort-Object -Unique } \ No newline at end of file From 27a5fc3d4e0ecae76a319234e5cfd59cbc60511e Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Sun, 26 May 2024 12:52:51 +0100 Subject: [PATCH 3/4] #1250, #1180: set integration test port to 60000, add retry around pwsh install --- .github/workflows/ci-pwsh_preview.yml | 10 +++++++--- tests/integration/Authentication.Tests.ps1 | 2 +- tests/integration/Endpoints.Tests.ps1 | 4 ++-- tests/integration/RestApi.Https.Tests.ps1 | 2 +- tests/integration/RestApi.Tests.ps1 | 2 +- tests/integration/Schedules.Tests.ps1 | 2 +- tests/integration/Sessions.Tests.ps1 | 2 +- tests/integration/Timers.Tests.ps1 | 2 +- tests/integration/WebPages.Tests.ps1 | 2 +- 9 files changed, 16 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci-pwsh_preview.yml b/.github/workflows/ci-pwsh_preview.yml index 110e3d92f..d20704cc4 100644 --- a/.github/workflows/ci-pwsh_preview.yml +++ b/.github/workflows/ci-pwsh_preview.yml @@ -23,9 +23,13 @@ jobs: - uses: actions/checkout@v4 - name: Setup Powershell - uses: bjompen/UpdatePWSHAction@v1.0.0 + uses: Wandalen/wretry.action@v3.5.0 with: - ReleaseVersion: 'Preview' + action: bjompen/UpdatePWSHAction@v1.0.0 + with: | + ReleaseVersion: 'Preview' + attempt_limit: 3 + attempt_delay: 10 - name: Check PowerShell version shell: pwsh @@ -44,7 +48,7 @@ jobs: - name: Run Pester Tests shell: pwsh - run: | + run: | Invoke-Build Test - name: Test docker builds diff --git a/tests/integration/Authentication.Tests.ps1 b/tests/integration/Authentication.Tests.ps1 index 382ab5836..9d5cb7130 100644 --- a/tests/integration/Authentication.Tests.ps1 +++ b/tests/integration/Authentication.Tests.ps1 @@ -11,7 +11,7 @@ BeforeAll { Describe 'Authentication Requests' { BeforeAll { - $Port = 50000 + $Port = 60000 $Endpoint = "http://127.0.0.1:$($Port)" Start-Job -Name 'Pode' -ErrorAction Stop -ScriptBlock { diff --git a/tests/integration/Endpoints.Tests.ps1 b/tests/integration/Endpoints.Tests.ps1 index b200e40eb..f9971ad75 100644 --- a/tests/integration/Endpoints.Tests.ps1 +++ b/tests/integration/Endpoints.Tests.ps1 @@ -4,10 +4,10 @@ param() Describe 'Endpoint Requests' { BeforeAll { - $Port1 = 50000 + $Port1 = 60000 $Endpoint1 = "http://127.0.0.1:$($Port1)" - $Port2 = 50001 + $Port2 = 60001 $Endpoint2 = "http://127.0.0.1:$($Port2)" Start-Job -Name 'Pode' -ErrorAction Stop -ScriptBlock { diff --git a/tests/integration/RestApi.Https.Tests.ps1 b/tests/integration/RestApi.Https.Tests.ps1 index dac2bbdbf..435f1d4db 100644 --- a/tests/integration/RestApi.Https.Tests.ps1 +++ b/tests/integration/RestApi.Https.Tests.ps1 @@ -45,7 +45,7 @@ public bool CheckValidationResult( } - $Port = 50010 + $Port = 60010 $Endpoint = "https://127.0.0.1:$($Port)" Start-Job -Name 'Pode' -ErrorAction Stop -ScriptBlock { diff --git a/tests/integration/RestApi.Tests.ps1 b/tests/integration/RestApi.Tests.ps1 index ec12dda16..eec69460e 100644 --- a/tests/integration/RestApi.Tests.ps1 +++ b/tests/integration/RestApi.Tests.ps1 @@ -5,7 +5,7 @@ param() Describe 'REST API Requests' { BeforeAll { - $Port = 50000 + $Port = 60000 $Endpoint = "http://127.0.0.1:$($Port)" Start-Job -Name 'Pode' -ErrorAction Stop -ScriptBlock { diff --git a/tests/integration/Schedules.Tests.ps1 b/tests/integration/Schedules.Tests.ps1 index 67f5e3af0..0c11f5b69 100644 --- a/tests/integration/Schedules.Tests.ps1 +++ b/tests/integration/Schedules.Tests.ps1 @@ -4,7 +4,7 @@ param() Describe 'Schedules' { BeforeAll { - $Port = 50000 + $Port = 60000 $Endpoint = "http://127.0.0.1:$($Port)" Start-Job -Name 'Pode' -ErrorAction Stop -ScriptBlock { diff --git a/tests/integration/Sessions.Tests.ps1 b/tests/integration/Sessions.Tests.ps1 index d77db8b90..6fb405837 100644 --- a/tests/integration/Sessions.Tests.ps1 +++ b/tests/integration/Sessions.Tests.ps1 @@ -5,7 +5,7 @@ param() Describe 'Session Requests' { BeforeAll { - $Port = 50000 + $Port = 60000 $Endpoint = "http://127.0.0.1:$($Port)" Start-Job -Name 'Pode' -ErrorAction Stop -ScriptBlock { diff --git a/tests/integration/Timers.Tests.ps1 b/tests/integration/Timers.Tests.ps1 index f11dd841b..f4c51d293 100644 --- a/tests/integration/Timers.Tests.ps1 +++ b/tests/integration/Timers.Tests.ps1 @@ -4,7 +4,7 @@ param() Describe 'Timers' { BeforeAll { - $Port = 50000 + $Port = 60000 $Endpoint = "http://127.0.0.1:$($Port)" Start-Job -Name 'Pode' -ErrorAction Stop -ScriptBlock { diff --git a/tests/integration/WebPages.Tests.ps1 b/tests/integration/WebPages.Tests.ps1 index b01b32c7a..487537eb8 100644 --- a/tests/integration/WebPages.Tests.ps1 +++ b/tests/integration/WebPages.Tests.ps1 @@ -3,7 +3,7 @@ param() Describe 'Web Page Requests' { BeforeAll { - $Port = 50000 + $Port = 60000 $Endpoint = "http://127.0.0.1:$($Port)" Start-Job -Name 'Pode' -ErrorAction Stop -ScriptBlock { From 059aa56608432c2f05414c59c962f2bb7391b6cf Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Sun, 26 May 2024 13:12:36 +0100 Subject: [PATCH 4/4] #1250, #1180: retry tool didnt support pwsh .... --- .github/workflows/ci-pwsh_preview.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci-pwsh_preview.yml b/.github/workflows/ci-pwsh_preview.yml index d20704cc4..57e72a678 100644 --- a/.github/workflows/ci-pwsh_preview.yml +++ b/.github/workflows/ci-pwsh_preview.yml @@ -23,13 +23,9 @@ jobs: - uses: actions/checkout@v4 - name: Setup Powershell - uses: Wandalen/wretry.action@v3.5.0 + uses: bjompen/UpdatePWSHAction@v1.0.0 with: - action: bjompen/UpdatePWSHAction@v1.0.0 - with: | - ReleaseVersion: 'Preview' - attempt_limit: 3 - attempt_delay: 10 + ReleaseVersion: 'Preview' - name: Check PowerShell version shell: pwsh