diff --git a/README.md b/README.md index 31a1fc0..bd3eb16 100644 --- a/README.md +++ b/README.md @@ -1,72 +1,79 @@ # Store A PowerShell module that manages a store of secrets and variables. -This module is designed to be a simple way to store and retrieve secrets and variables in a PowerShell script or module. + +The main purpose of the module is to provide a standard way to store and retrieve +module and user configuration for PowerShell modules. The module builds a very thin overlay of functions ontop of the +`Microsoft.PowerShell.SecretManagement` and `Microsoft.PowerShell.SecretStore` modules. As they do not have a good way to manage modifying specific +values in the metadata of a secret, this module provides a way to do that. ## Prerequisites -This module relies on [Microsoft.PowerShell.SecretManagement](https://github.com/powershell/SecretManagement) and -[Microsoft.PowerShell.SecretStore](https://github.com/PowerShell/SecretStore) by default. You can use other secret vault -providers by installing them and setting them as the provider when calling the function. +This module relies on [Microsoft.PowerShell.SecretManagement](https://GitHub.com/powershell/SecretManagement) and +[Microsoft.PowerShell.SecretStore](https://GitHub.com/PowerShell/SecretStore). ## Installation -Provide step-by-step instructions on how to install the module, including any InstallModule commands or manual installation steps. +Install the module from the PowerShell Gallery by running the following command: ```powershell -Install-Module -Name Store +Install-PSResource -Name Store -TrustRepository -Repository PSGallery Import-Module -Name Store ``` ## Usage -Here is a list of example that are typical use cases for the module. -This section should provide a good overview of the module's capabilities. +Modules usually have two types of data that would be great to store, module and user configuration. With this module we aim to store this data +separate from the module code, so that modules can be created in a way where users can resume from where they left off without having to reconfigure +the module or log in to services that support refreshing sessions with data you can store, i.e. refresh tokens. -### Initialize the store +### Module configuration -The following command creates a new store with the name 'MyStore'. This results in a `config.json` file being created in `$HOME\.mystore\`. -It also ensures there is a secret vault provider created called 'SecretStore' and sets it as the default provider for the store. +To store module configuration, the module developer can create a secret in the store that defines a "namespace" for the module configuration. All +other configurations done by the module will be stored with a name that is prefixed with the namespace. The secret metadata is where the configuration +is stored. The secret value itself it not used for the namespace secrets. -If a store already exists with the type 'Microsoft.PowerShell.SecretStore', it will be used as the default provider for the store. +Lets say we have a module called `GitHub` that needs to store configuration. The module developer would initialize a store called 'GitHub'. All module +configuration would be stored in this secret. All other configutations would be stored "in" the `GitHub` store, when in reality they are stored flat +in the SecretStore, but uses a hierarchy based naming convention to group the secrets together. -```powershell -Initialize-Store -Name 'MyStore' -``` +### User configuration -### Add a variable to the store +To store user configuration, the module developer can create a secret in the store that defines a "namespace" for the user configuration within the +store they have defined. So lets say a developer has implemented this for the `GitHub` module, a user would log in using their details. The module +would call upon `Store` functionality to create a new context under the `GitHub` store. -The following command adds a variable to the store with the name 'MyVariable' and the value 'Something'. +Imagine a user called `BobMarley` logs in to the `GitHub` module. The following would exist in the store: -```powershell -Add-StoreConfig -Name 'MyVariable' -Value 'Something' -``` +- `GitHub` containing module configuration, like default user, host and client ID to use if not otherwise specified. +- `GitHub.BobMarley` containing user configuration +- `GitHub.BobMarley.AccessToken` containing the access token for the user with the validity stored in the metadata +- `GitHub.BobMarley.RefreshToken` containing the refresh token for the user with the validity stored in the metadata -As the value is not a secure string, it will be stored in plain text in the store json file. +Lets say the person also has another account on GitHub called `RastaBlasta`. After logging on with the second account the store would also have: -### Add a secret to the store +- `GitHub.RastaBlasta` containing user configuration +- `GitHub.RastaBlasta.AccessToken` containing the access token for the user with the validity stored in the metadata +- `GitHub.RastaBlasta.RefreshToken` containing the refresh token for the user with the validity stored in the metadata -The following command adds a secret to the store with the name 'MySecret' and the value 'Something'. The secret is stored in the default provider. +### Setup for a new module -```powershell -Add-StoreConfig -Name 'MySecret' -Value ('Something' | ConvertTo-SecureString -AsPlainText -Force) -``` +To setup a new module to use the `Store` module, the following steps should be taken: -As the value is a secure string, it will be stored securely in the secret vault. +1. Create a new store for the module -> `Set-Store -Name 'GitHub'` +2. Add some module configuration -> `Set-StoreConfig -Store 'GitHub' -Name 'ClientId' -Value '123456'` +3. Get the module configuration -> `Get-StoreConfig -Store 'GitHub' -Name 'ClientId'` -> `123456` + - Get-StoreData -Store 'GitHub' -> Returns all module configuration for the `GitHub` store. +4. Remove the module configuration -> `Remove-StoreConfig -Store 'GitHub' -Name 'ClientId'` -### Get a variable or secret from the store +### Setup for a new context -The following command gets the value of the variable 'MyVariable' from the store. +To setup a new context for a user, the following steps should be taken: -```powershell -Get-StoreConfig -Name 'MyVariable' -``` - -The following command gets the value of the secret 'MySecret' from the store. - -```powershell -Get-StoreConfig -Name 'MySecret' -``` +1. Create a new context for the user -> `Set-Store -Store 'GitHub.BobMarley'` -> Secret `GitHub.BobMarley` is created. +2. Add some user configuration -> `Set-StoreConfig -Store 'GitHub.BobMarley.AccessToken' -Name 'Secret' -Value '123456'` -> Secret `GitHub.BobMarley.AccessToken` is created. +3. Get the user configuration -> `Get-StoreConfig -Store 'GitHub.BobMarley.AccessToken' -Name 'Secret' -AsPlainText` -> `123456` +4. Remove the user configuration -> `Remove-Store -Name 'GitHub.BobMarley.AccessToken'` -> Secret `GitHub.BobMarley.AccessToken` is removed. ## Contributing @@ -85,6 +92,6 @@ You can either help by picking up an existing issue or submit a new one if you h ## Links -- SecretManagement | [GitHub](https://github.com/powershell/SecretManagement) | [Docs](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.secretmanagement/?view=ps-modules) -- SecretStore | [GitHub](https://github.com/PowerShell/SecretStore) | [Docs](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.secretstore/?view=ps-modules) +- SecretManagement | [GitHub](https://GitHub.com/powershell/SecretManagement) | [Docs](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.secretmanagement/?view=ps-modules) +- SecretStore | [GitHub](https://GitHub.com/PowerShell/SecretStore) | [Docs](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.secretstore/?view=ps-modules) - [Overview of the SecretManagement and SecretStore modules | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/secretmanagement/overview?view=ps-modules) diff --git a/src/functions/private/Get-StoreVariable.ps1 b/src/functions/private/Get-StoreVariable.ps1 deleted file mode 100644 index 8d964fe..0000000 --- a/src/functions/private/Get-StoreVariable.ps1 +++ /dev/null @@ -1,28 +0,0 @@ -function Get-StoreVariable { - <# - .SYNOPSIS - Get a variable from the store. - - .EXAMPLE - Get-StoreVariable - - Gets all the variables in the store. - - .EXAMPLE - Get-StoreVariable -Name 'Name' - - Gets the value of the variable with the name 'Name'. - #> - [CmdletBinding()] - [OutputType([object])] - param( - [Parameter()] - [string] $Name - ) - - if (-not $Name) { - $script:Store - } else { - $script:Store.$Name - } -} diff --git a/src/functions/private/Initialize-SecretStore.ps1 b/src/functions/private/Initialize-SecretStore.ps1 deleted file mode 100644 index 1f4f6c3..0000000 --- a/src/functions/private/Initialize-SecretStore.ps1 +++ /dev/null @@ -1,75 +0,0 @@ -#Requires -Modules Microsoft.PowerShell.SecretManagement -#Requires -Modules Microsoft.PowerShell.SecretStore - -function Initialize-SecretStore { - <# - .SYNOPSIS - Initialize a secret vault. - - .DESCRIPTION - Initialize a secret vault. If the vault does not exist, it will be created. - - .EXAMPLE - Initialize-SecretStore -Name 'SecretStore' -Type 'Microsoft.PowerShell.SecretStore' - - Initializes a secret vault named 'SecretStore' using the 'Microsoft.PowerShell.SecretStore' module. - - .NOTES - For more information about secret vaults, see - https://learn.microsoft.com/en-us/powershell/utility-modules/secretmanagement/overview?view=ps-modules - #> - [OutputType([void])] - [CmdletBinding()] - param ( - # The name of the secret vault. - [Parameter()] - [string] $Name = 'SecretStore', - - # The type of the secret vault. - [Parameter()] - [Alias('ModuleName')] - [string] $Type = 'Microsoft.PowerShell.SecretStore' - ) - - $vault = Get-SecretVault | Where-Object { $_.ModuleName -eq $Type } - if (-not $vault) { - Write-Verbose "[$Type] - Registering" - - switch ($Type) { - 'Microsoft.PowerShell.SecretStore' { - $vaultParameters = @{ - Authentication = 'None' - PasswordTimeout = -1 - Interaction = 'None' - Scope = 'CurrentUser' - WarningAction = 'SilentlyContinue' - Confirm = $false - Force = $true - } - Reset-SecretStore @vaultParameters - } - } - Write-Verbose "[$Type] - Done" - } else { - Write-Verbose "[$Type] - already registered" - } - - $secretStore = Get-SecretVault | Where-Object { $_.Name -eq $Name } - if (-not $secretStore) { - Write-Verbose "[$Name] - Registering" - $secretVault = @{ - Name = $Name - ModuleName = $Type - DefaultVault = $true - Description = 'SecretStore' - } - Register-SecretVault @secretVault - Write-Verbose "[$Name] - Done" - } else { - Write-Verbose "[$Name] - already registered" - } - - Set-StoreVariable -Name 'SecretVaultName' -Value $Name - Set-StoreVariable -Name 'SecretVaultType' -Value $Type - -} diff --git a/src/functions/private/Initialize-SecretVault.ps1 b/src/functions/private/Initialize-SecretVault.ps1 new file mode 100644 index 0000000..926cc03 --- /dev/null +++ b/src/functions/private/Initialize-SecretVault.ps1 @@ -0,0 +1,69 @@ +#Requires -Modules Microsoft.PowerShell.SecretManagement +#Requires -Modules Microsoft.PowerShell.SecretStore + +function Initialize-SecretVault { + <# + .SYNOPSIS + Initialize a SecretStore with open config. + + .DESCRIPTION + Initialize a secret vault. If the vault does not exist, it will be created and registered. + + The SecretStore is created with the following parameters: + - Authentication: None + - PasswordTimeout: -1 (infinite) + - Interaction: None + - Scope: CurrentUser + + .EXAMPLE + Initialize-SecretStore + + Initializes a secret vault named 'SecretStore' using the 'Microsoft.PowerShell.SecretStore' module. + + .NOTES + For more information about secret vaults, see + https://learn.microsoft.com/en-us/powershell/utility-modules/secretmanagement/overview?view=ps-modules + #> + [OutputType([Microsoft.PowerShell.SecretManagement.SecretVaultInfo])] + [CmdletBinding()] + param ( + # The name of the secret vault. + [Parameter()] + [string] $Name = $script:Config.SecretVaultName, + + # The type of the secret vault. + [Parameter()] + [string] $Type = $script:Config.SecretVaultType + ) + $vault = Get-SecretVault | Where-Object { $_.ModuleName -eq $Type } + if (-not $vault) { + Write-Verbose "[$Type] - Configuring vault type" + + $vaultParameters = @{ + Authentication = 'None' + PasswordTimeout = -1 + Interaction = 'None' + Scope = 'CurrentUser' + WarningAction = 'SilentlyContinue' + Confirm = $false + Force = $true + } + Reset-SecretStore @vaultParameters + Write-Verbose "[$Type] - Done" + + Write-Verbose "[$Name] - Registering vault" + $secretVault = @{ + Name = $Name + ModuleName = $Type + DefaultVault = $true + Description = 'SecretStore' + } + Register-SecretVault @secretVault + Write-Verbose "[$Name] - Done" + } else { + Write-Verbose "[$Name] - Vault already registered" + } + + Get-SecretVault | Where-Object { $_.ModuleName -eq $Type } + +} diff --git a/src/functions/private/Initialize-VariableStore.ps1 b/src/functions/private/Initialize-VariableStore.ps1 deleted file mode 100644 index 47e1ac3..0000000 --- a/src/functions/private/Initialize-VariableStore.ps1 +++ /dev/null @@ -1,27 +0,0 @@ -function Initialize-VariableStore { - <# - .SYNOPSIS - Initialize the variable store. - #> - [CmdletBinding()] - [OutputType([void])] - param ( - [Parameter(Mandatory)] - [string] $Name - ) - - $folderName = ".$($Name -replace '^\.')".ToLower() - Write-Verbose "Variable store folder: [$folderName]" - $configFilePath = Join-Path -Path $HOME -ChildPath "$folderName/config.json" - Write-Verbose "Variable store file: [$configFilePath]" - $configFileExists = Test-Path -Path $configFilePath - Write-Verbose "Variable store file exists: [$configFileExists]" - if (-not $configFileExists) { - $null = New-Item -Path $configFilePath -ItemType File -Force - Set-StoreVariable -Name 'ConfigFilePath' -Value $configFilePath - Set-StoreVariable -Name 'Name' -Value $Name - } - - $script:Store = Get-Content -Path $configFilePath | ConvertFrom-Json - -} diff --git a/src/functions/private/Set-StoreVariable.ps1 b/src/functions/private/Set-StoreVariable.ps1 deleted file mode 100644 index 208c50b..0000000 --- a/src/functions/private/Set-StoreVariable.ps1 +++ /dev/null @@ -1,29 +0,0 @@ -function Set-StoreVariable { - <# - .SYNOPSIS - Set a variable in the store. - - .EXAMPLE - Set-StoreVariable -Name 'Name' -Value 'MyName' - #> - [CmdletBinding(SupportsShouldProcess)] - param( - # The name of the variable to set. - [Parameter(Mandatory)] - [string] $Name, - - # The value to set. - [Parameter(Mandatory)] - [AllowNull()] - [object] $Value - ) - - if ($PSCmdlet.ShouldProcess("Set variable '$Name' to '$Value'")) { - if ($null -eq $Value) { - $script:Store.PSObject.Properties.Remove($Name) - } else { - $script:Store | Add-Member -MemberType NoteProperty -Name $Name -Value $Value -Force - } - $script:Store | ConvertTo-Json -Depth 100 | Set-Content -Path $script:Store.ConfigFilePath -Force - } -} diff --git a/src/functions/public/Get-StoreConfig.ps1 b/src/functions/public/Get-StoreConfig.ps1 deleted file mode 100644 index 252b7ab..0000000 --- a/src/functions/public/Get-StoreConfig.ps1 +++ /dev/null @@ -1,47 +0,0 @@ -#Requires -Modules Microsoft.PowerShell.SecretManagement - -function Get-StoreConfig { - <# - .SYNOPSIS - Get configuration value. - - .DESCRIPTION - Get a named configuration value from the store configuration. - - .EXAMPLE - Get-StoreConfig -Name ApiBaseUri - - Get the value of ApiBaseUri config. - #> - [OutputType([object])] - [CmdletBinding()] - param ( - # Choose a configuration name to get. - [Parameter()] - [string] $Name, - - # Return the value as plain text if it is a secret. - [Parameter()] - [switch] $AsPlainText - ) - - if (-not $Name) { - return [pscustomobject]@{ - Secrets = Get-SecretInfo | ForEach-Object { - [pscustomobject]@{ - Name = $_.Name - Value = Get-Secret -Name $_.Name -AsPlainText:$AsPlainText -Vault $script:Store.SecretVaultName - } - } - Variables = $script:Store - } - } - - $value = Get-StoreVariable -Name $Name - - if (($null -eq $value) -and ((Get-SecretInfo -Vault $script:Store.SecretVaultName).Name -contains $Name)) { - $value = Get-Secret -Name $Name -AsPlainText:$AsPlainText -Vault $script:Store.SecretVaultName - } - - $value -} diff --git a/src/functions/public/Initialize-Store.ps1 b/src/functions/public/Initialize-Store.ps1 deleted file mode 100644 index ae479bc..0000000 --- a/src/functions/public/Initialize-Store.ps1 +++ /dev/null @@ -1,26 +0,0 @@ -function Initialize-Store { - <# - .SYNOPSIS - Initialize the store for a module. - - .EXAMPLE - Initialize-Store -Name 'MyStore' - #> - [CmdletBinding()] - param ( - # Name of the store. - [Parameter(Mandatory)] - [string] $Name, - - # The name of the secret vault. - [Parameter()] - [string] $SecretVaultName = 'SecretStore', - - # The type of the secret vault. - [Parameter()] - [string] $SecretVaultType = 'Microsoft.PowerShell.SecretStore' - ) - - Initialize-VariableStore -Name $Name - Initialize-SecretStore -Name $SecretVaultName -Type $SecretVaultType -} diff --git a/src/functions/public/Set-StoreConfig.ps1 b/src/functions/public/Set-StoreConfig.ps1 deleted file mode 100644 index 375c7be..0000000 --- a/src/functions/public/Set-StoreConfig.ps1 +++ /dev/null @@ -1,40 +0,0 @@ -#Requires -Modules Microsoft.PowerShell.SecretManagement - -function Set-StoreConfig { - <# - .SYNOPSIS - Set a configuration variables or secret. - - .DESCRIPTION - Set a configuration variable or secret in the configuration store. - - .EXAMPLE - Set-StoreConfig -VariableName "ApiBaseUri" -Value 'https://api.github.com' - - Sets a variable called 'ApiBaseUri' in the configuration store (json file). - - .EXAMPLE - Set-StoreConfig -SecretName "AccessToken" -Value 'myAccessToken' - - Sets a secret called 'AccessToken' in the configuration store (secret vault). - #> - [CmdletBinding(SupportsShouldProcess)] - param ( - # The name of a variable to set. - [Parameter(Mandatory)] - [string] $Name, - - # The value to set. - [Parameter(Mandatory)] - [AllowNull()] - [object] $Value - ) - - if ($PSCmdlet.ShouldProcess("Set variable '$Name' to '$Value'")) { - if ($Value -is [SecureString]) { - Set-Secret -Name $Name -SecureStringSecret $Value - } else { - Set-StoreVariable -Name $Name -Value $Value - } - } -} diff --git a/src/functions/public/Store/Get-Store.ps1 b/src/functions/public/Store/Get-Store.ps1 new file mode 100644 index 0000000..bc688a8 --- /dev/null +++ b/src/functions/public/Store/Get-Store.ps1 @@ -0,0 +1,36 @@ +function Get-Store { + <# + .SYNOPSIS + Get a store from the vault. + + .DESCRIPTION + Get a store from the vault. + + .EXAMPLE + Get-Store -Name 'MySecret' + + Get the store called 'MySecret' from the vault. + #> + [OutputType([hashtable])] + param ( + # The name of the secret vault. + [Parameter(Mandatory)] + [string] $Name, + + # Set everything as plain text. + [Parameter()] + [switch] $AsPlainText + ) + $secretVault = Get-SecretVault | Where-Object { $_.Name -eq $script:Config.SecretVaultName } + if (-not $secretVault) { + return $null + } + $secretInfo = Get-SecretInfo -Vault $secretVault.Name | Where-Object { $_.Name -eq $Name } + if (-not $secretInfo) { + return $null + } + $metadata = $secretInfo | Select-Object -ExpandProperty Metadata + $metadata + @{ + Secret = Get-Secret -Name $Name -Vault $script:Config.SecretVaultName -AsPlainText:$AsPlainText + } +} diff --git a/src/functions/public/Store/Remove-Store.ps1 b/src/functions/public/Store/Remove-Store.ps1 new file mode 100644 index 0000000..38d3e3a --- /dev/null +++ b/src/functions/public/Store/Remove-Store.ps1 @@ -0,0 +1,26 @@ +function Remove-Store { + <# + .SYNOPSIS + Remove a store from the vault. + + .DESCRIPTION + Remove a store from the vault. + + .EXAMPLE + Remove-Store -Name 'MySecret' + + Removes the store called 'MySecret' from the vault. + #> + [OutputType([void])] + [CmdletBinding(SupportsShouldProcess)] + param ( + # The name of the secret vault. + [Parameter(Mandatory)] + [string] $Name + ) + if ($PSCmdlet.ShouldProcess('Remove-Secret', $Name)) { + Get-SecretInfo | Where-Object { $_.Name -eq $Name } | ForEach-Object { + Remove-Secret -Name $_.Name -Vault $script:Config.SecretVaultName + } + } +} diff --git a/src/functions/public/Store/Set-Store.ps1 b/src/functions/public/Store/Set-Store.ps1 new file mode 100644 index 0000000..7c8747b --- /dev/null +++ b/src/functions/public/Store/Set-Store.ps1 @@ -0,0 +1,50 @@ +function Set-Store { + <# + .SYNOPSIS + Set a store in the vault. + + .DESCRIPTION + If the store does not exist, it will be created. If it already exists, it will be updated. + + .EXAMPLE + Set-Store -Name 'MySecret' + + Create a store called 'MySecret' in the vault. + + .EXAMPLE + Set-Store -Name 'MySecret' -Secret 'MySecret' + + Creates a store called 'MySecret' in the vault with the secret. + + .EXAMPLE + Set-Store -Name 'MySecret' -Secret 'MySecret' -Variables @{ 'Key' = 'Value' } + + Creates a store called 'MySecret' in the vault with the secret and variables. + #> + [OutputType([void])] + [CmdletBinding(SupportsShouldProcess)] + param ( + # The name of the store. + [Parameter()] + [string] $Name, + + # The secret of the store. + [Parameter()] + [string] $Secret = 'null', + + # The variables of the store. + [Parameter()] + [hashtable] $Variables + ) + $param = @{ + Name = $Name + Secret = $Secret + Vault = $script:Config.SecretVaultName + } + if ($Variables) { + $param.Metadata = $Variables + } + if ($PSCmdlet.ShouldProcess('Set-Secret', $param)) { + Set-Secret @param + } +} diff --git a/src/functions/public/StoreConfig/Get-StoreConfig.ps1 b/src/functions/public/StoreConfig/Get-StoreConfig.ps1 new file mode 100644 index 0000000..e49fc84 --- /dev/null +++ b/src/functions/public/StoreConfig/Get-StoreConfig.ps1 @@ -0,0 +1,33 @@ +#Requires -Modules Microsoft.PowerShell.SecretManagement + +function Get-StoreConfig { + <# + .SYNOPSIS + Get a named value from the store. + + .DESCRIPTION + Get a named value from the store. + + .EXAMPLE + Get-StoreConfig -Name 'ApiBaseUri' -Store 'GitHub' + + Get the value of 'ApiBaseUri' config from the GitHub store. + #> + [OutputType([object])] + [CmdletBinding()] + param ( + # Name of a value to get. + [Parameter(Mandatory)] + [string] $Name, + + # Return the value as plain text if it is a secret. + [Parameter()] + [switch] $AsPlainText, + + # The store to get the configuration from. + [Parameter()] + [string] $Store + ) + + (Get-Store -Name $Store -AsPlainText:$AsPLainText).$Name +} diff --git a/src/functions/public/StoreConfig/Remove-StoreConfig.ps1 b/src/functions/public/StoreConfig/Remove-StoreConfig.ps1 new file mode 100644 index 0000000..96b751b --- /dev/null +++ b/src/functions/public/StoreConfig/Remove-StoreConfig.ps1 @@ -0,0 +1,29 @@ +function Remove-StoreConfig { + <# + .SYNOPSIS + Remove a named value from the store. + + .DESCRIPTION + Remove a named value from the store. + + .EXAMPLE + Remove-StoreConfig -Name 'ApiBaseUri' -Store 'GitHub' + + Remove the ApiBaseUri value from the 'GitHub' store. + #> + [CmdletBinding(SupportsShouldProcess)] + param ( + # Name of a value to remove. + [Parameter(Mandatory)] + [string] $Name, + + # The store to remove the value from. + [Parameter()] + [string] $Store + ) + + if ($PSCmdlet.ShouldProcess("config '$Name' from '$Store'", 'Remove')) { + + } + Set-StoreConfig -Store $Store -Name $Name -Value $null +} diff --git a/src/functions/public/StoreConfig/Set-StoreConfig.ps1 b/src/functions/public/StoreConfig/Set-StoreConfig.ps1 new file mode 100644 index 0000000..35d706f --- /dev/null +++ b/src/functions/public/StoreConfig/Set-StoreConfig.ps1 @@ -0,0 +1,69 @@ +#Requires -Modules Microsoft.PowerShell.SecretManagement + +function Set-StoreConfig { + <# + .SYNOPSIS + Set a variables or secret. + + .DESCRIPTION + Set a variable or secret in the store. + To store a secret, set the name to 'Secret'. + + .EXAMPLE + Set-StoreConfig -Name 'ApiBaseUri' -Value 'https://api.github.com' -Store 'GitHub' + + Sets a variable called 'ApiBaseUri' in the store called 'GitHub'. + + .EXAMPLE + $secret = 'myAccessToken' | ConvertTo-SecureString -AsPlainText -Force + Set-StoreConfig -Name 'Secret' -Value $secret -Store 'GitHub' + + Sets a secret called 'AccessToken' in the configuration store called 'GitHub'. + #> + [CmdletBinding(SupportsShouldProcess)] + param ( + # The name of a value to set. + [Parameter(Mandatory)] + [string] $Name, + + # The value to set. + [Parameter(Mandatory)] + [AllowNull()] + [AllowEmptyString()] + [object] $Value, + + # The name of the store. + [Parameter(Mandatory)] + [string] $Store + ) + + $secretVault = Get-SecretVault | Where-Object { $_.Name -eq $script:Config.SecretVaultName } + if (-not $secretVault) { + throw "Vault '$($script:Config.SecretVaultName)' not found" + } + + if ($PSCmdlet.ShouldProcess($Name, "Set value $Value]")) { + if ($Name -eq 'Secret') { + if ([string]::IsNullOrEmpty($Value)) { + $Value = 'null' + } + if ($Value -is [SecureString]) { + Set-Secret -Name $Store -SecureStringSecret $Value -Vault $script:Config.SecretVaultName + } else { + Set-Secret -Name $Store -Value $Value -Vault $script:Config.SecretVaultName + } + } else { + $secretInfo = Get-SecretInfo -Vault $secretVault.Name | Where-Object { $_.Name -eq $Store } + if (-not $secretInfo) { + throw "Store '$Store' not found" + } + $metadata = ($secretInfo | Select-Object -ExpandProperty Metadata) + @{} + if ([string]::IsNullOrEmpty($Value)) { + $metadata.Remove($Name) + } else { + $metadata[$Name] = $Value + } + Set-SecretInfo -Name $Store -Metadata $metadata -Vault $script:Config.SecretVaultName + } + } +} diff --git a/src/loader.ps1 b/src/loader.ps1 new file mode 100644 index 0000000..8337c35 --- /dev/null +++ b/src/loader.ps1 @@ -0,0 +1,19 @@ + +### This is the backend configuration for the functionality +$initStoreParams = @{ + Name = (Get-StoreConfig -Name SecretVaultName -Store $script:Config.Name) ?? $script:Config.SecretVaultName + Type = (Get-StoreConfig -Name SecretVaultType -Store $script:Config.Name) ?? $script:Config.SecretVaultType +} +$vault = Initialize-SecretVault @initStoreParams +$script:Config.SecretVaultName = $vault.Name +$script:Config.SecretVaultType = $vault.ModuleName + +### This is the store config for this module +$storeParams = @{ + Name = $script:Config.Name + Variables = @{ + SecretVaultName = $script:Config.SecretVaultName + SecretVaultType = $script:Config.SecretVaultType + } +} +Set-Store @storeParams diff --git a/src/variables/private/Config.ps1 b/src/variables/private/Config.ps1 new file mode 100644 index 0000000..8bff4b8 --- /dev/null +++ b/src/variables/private/Config.ps1 @@ -0,0 +1,5 @@ +$script:Config = @{ + Name = 'PSModule.Store' # $script:Config.Name + SecretVaultName = 'SecretStore' # $script:Config.SecretVaultName + SecretVaultType = 'Microsoft.PowerShell.SecretStore' # $script:Config.SecretVaultType +} diff --git a/src/variables/private/Store.ps1 b/src/variables/private/Store.ps1 deleted file mode 100644 index 7cf0c8c..0000000 --- a/src/variables/private/Store.ps1 +++ /dev/null @@ -1 +0,0 @@ -$script:Store = [PSCustomObject]@{} diff --git a/tests/Store.Tests.ps1 b/tests/Store.Tests.ps1 index b27416a..e8d0a5b 100644 --- a/tests/Store.Tests.ps1 +++ b/tests/Store.Tests.ps1 @@ -5,43 +5,30 @@ param() Describe 'Store' { - Context 'Initialize-Store' { + Context 'Set-Store' { It 'Should be available' { - Get-Command -Name 'Initialize-Store' | Should -Not -BeNullOrEmpty + Get-Command -Name 'Set-Store' | Should -Not -BeNullOrEmpty } - It 'Should be able to run' { - { Initialize-Store -Name 'GitHub' } | Should -Not -Throw + It "Set-Store -Name 'Test'" { + { Set-Store -Name 'Test' } | Should -Not -Throw + } + It "Set-Store -Name 'Test' - a second time" { + { Set-Store -Name 'Test' } | Should -Not -Throw } - It 'Should be able to run multiple times without erroring out' { - { Initialize-Store -Name 'GitHub' } | Should -Not -Throw + It "Set-Store -Name 'Test' -Variables @{ 'Test' = 'Test' }" { + { Set-Store -Name 'Test' -Variables @{ 'Test' = 'Test' } } | Should -Not -Throw } + } - Context 'Get-StoreConfig' { + Context 'Get-Store' { It 'Should be available' { - Get-Command -Name 'Get-StoreConfig' | Should -Not -BeNullOrEmpty - } - It 'Should be able to run without parameters' { - Write-Verbose (Get-StoreConfig | ConvertTo-Json) -Verbose - { Get-StoreConfig } | Should -Not -Throw - } - It 'Should be able to run with parameters' { - { Get-StoreConfig -Name 'Name' } | Should -Not -Throw + Get-Command -Name 'Get-Store' | Should -Not -BeNullOrEmpty } - It 'Should be able to get its own name' { - $name = Get-StoreConfig -Name 'Name' - $name | Should -Be 'GitHub' + It "Get-Store -Name 'Test'" { + { Get-Store -Name 'Test' } | Should -Not -Throw } - It 'Should be able to get its own path' { - $configFilePath = Get-StoreConfig -Name 'ConfigFilePath' - $configFilePath | Should -Be (Join-Path -Path $HOME -ChildPath '.github/config.json') - } - It 'Should be able to get the secret vault name' { - $secretVaultName = Get-StoreConfig -Name 'SecretVaultName' - $secretVaultName | Should -Be 'SecretStore' - } - It 'Should be able to get the secret vault type' { - $secretVaultType = Get-StoreConfig -Name 'SecretVaultType' - $secretVaultType | Should -Be 'Microsoft.PowerShell.SecretStore' + It "Get-Store -Name 'Test' -AsPlainText" { + { Get-Store -Name 'Test' -AsPlainText } | Should -Not -Throw } } Context 'Set-StoreConfig' { @@ -49,22 +36,51 @@ Describe 'Store' { Get-Command -Name 'Set-StoreConfig' | Should -Not -BeNullOrEmpty } It 'Should be able to run' { - { Set-StoreConfig -Name 'Something' -Value 'Something' } | Should -Not -Throw - } - It 'Should be able to set a variable' { - Set-StoreConfig -Name 'Something' -Value 'Something' - $something = Get-StoreConfig -Name 'Something' - $something | Should -Be 'Something' + { Set-StoreConfig -Name 'Something' -Value 'Something' -Store 'Test' } | Should -Not -Throw } It 'Should be able to set a secret' { - Set-StoreConfig -Name 'Secret' -Value ('Something' | ConvertTo-SecureString -AsPlainText -Force) - $secret = Get-StoreConfig -Name 'Secret' -AsPlainText + $secretValue = 'Something' | ConvertTo-SecureString -AsPlainText -Force + Set-StoreConfig -Name 'Secret' -Value $secretValue -Store 'Test' + $secret = Get-StoreConfig -Name 'Secret' -AsPlainText -Store 'Test' $secret | Should -Be 'Something' } It 'Should be able to remove a variable if set to $null' { - Set-StoreConfig -Name 'Something' -Value $null - $something = Get-StoreConfig -Name 'Something' + Set-StoreConfig -Name 'Something' -Value $null -Store 'Test' + $something = Get-StoreConfig -Name 'Something' -Store 'Test' $something | Should -BeNullOrEmpty } } + Context 'Get-StoreConfig' { + It 'Should be available' { + Get-Command -Name 'Get-StoreConfig' | Should -Not -BeNullOrEmpty + } + It 'Should NOT be able to run without parameters' { + { Get-StoreConfig } | Should -Throw + } + It 'Should be able to try to get a value that doesnt exists' { + $value = Get-StoreConfig -Name 'Something' -Store 'Test' + $value | Should -BeNullOrEmpty + } + It 'Should be able to run with parameters' { + Set-StoreConfig -Name 'Something' -Value 'Something' -Store 'Test' + { Get-StoreConfig -Name 'Something' -Store 'Test' } | Should -Not -Throw + } + } + Context 'Remove-StoreConfig' { + It 'Should be available' { + Get-Command -Name 'Remove-StoreConfig' | Should -Not -BeNullOrEmpty + } + It 'Should be able to run' { + Set-StoreConfig -Name 'Something' -Value 'Something' -Store 'Test' + { Remove-StoreConfig -Name 'Something' -Store 'Test' } | Should -Not -Throw + } + } + Context 'Remove-Store' { + It 'Should be available' { + Get-Command -Name 'Remove-Store' | Should -Not -BeNullOrEmpty + } + It 'Should be able to run' { + { Remove-Store -Name 'Test' } | Should -Not -Throw + } + } }