From 7e85861bd92e42d775e2ec7889943a738f2a1828 Mon Sep 17 00:00:00 2001 From: harrisonmeister Date: Thu, 9 Oct 2025 17:27:08 +0100 Subject: [PATCH] Add Bitwarden Secrets Manager - retrieve secrets steps --- gulpfile.babel.js | 2 + ...rden-secrets-manager-retrieve-secrets.json | 75 ++++++++++++++++++ step-templates/logos/bitwarden.png | Bin 0 -> 10512 bytes 3 files changed, 77 insertions(+) create mode 100644 step-templates/bitwarden-secrets-manager-retrieve-secrets.json create mode 100644 step-templates/logos/bitwarden.png diff --git a/gulpfile.babel.js b/gulpfile.babel.js index bb441ee37..8745c1e28 100644 --- a/gulpfile.babel.js +++ b/gulpfile.babel.js @@ -127,6 +127,8 @@ function humanize(categoryId) { return "Azure Site Extensions"; case "azureFunctions": return "Azure Functions"; + case "bitwarden": + return "Bitwarden"; case "cassandra": return "Cassandra"; case "chef": diff --git a/step-templates/bitwarden-secrets-manager-retrieve-secrets.json b/step-templates/bitwarden-secrets-manager-retrieve-secrets.json new file mode 100644 index 000000000..36b2550a1 --- /dev/null +++ b/step-templates/bitwarden-secrets-manager-retrieve-secrets.json @@ -0,0 +1,75 @@ +{ + "Id": "740fb4a1-e863-4e81-ab54-ef28292334e4", + "Name": "Bitwarden Secrets Manager - Retrieve Secrets", + "Description": "This step retrieves one or more secrets from [Bitwarden Secrets Manager](https://bitwarden.com/products/secrets-manager/), and creates [sensitive output variables](https://octopus.com/docs/projects/variables/output-variables#sensitive-output-variables) for each value retrieved. These values can be used in other steps in your deployment or runbook process.\n\nYou can choose a custom output variable name for each secret, or one will be chosen for you.\n\n---\n\n**Required:** \n- PowerShell **5.1** or higher.\n- The Bitwarden Secrets Manager (`bws`) CLI installed on the target or worker. If the CLI can't be found, the step will fail.\n- A machine account [access token](https://bitwarden.com/help/access-tokens/) with permissions to retrieve secrets from the specified project.\n\nNotes:\n\n- Tested on Octopus **2025.4**.\n- Tested on both Windows Server 2022 and Ubuntu 24.04.", + "ActionType": "Octopus.Script", + "Version": 1, + "CommunityActionTemplateId": null, + "Packages": [], + "GitDependencies": [], + "Properties": { + "Octopus.Action.Script.ScriptSource": "Inline", + "Octopus.Action.Script.Syntax": "PowerShell", + "Octopus.Action.Script.ScriptBody": "$ErrorActionPreference = 'Stop'\n\n# Variables\n$BwsServerUrl = $OctopusParameters[\"Bitwarden.SecretsManager.RetrieveSecrets.ServerUrl\"]\n$ProjectName = $OctopusParameters[\"Bitwarden.SecretsManager.RetrieveSecrets.ProjectName\"]\n$BwsAccessToken = $OctopusParameters[\"Bitwarden.SecretsManager.RetrieveSecrets.AccessToken\"]\n$SecretNames = $OctopusParameters[\"Bitwarden.SecretsManager.RetrieveSecrets.SecretNames\"]\n$PrintVariableNames = $OctopusParameters[\"Bitwarden.SecretsManager.RetrieveSecrets.PrintVariableNames\"]\n\nWrite-Output \"Verifying 'bws' command availability...\"\nif (-not (Get-Command bws -ErrorAction SilentlyContinue)) {\n throw \"The 'bws' (Bitwarden Secrets Manager CLI) command was not found. Please ensure it is installed and available in the system's PATH.\"\n}\nWrite-Output \"'bws' command found.\"\n\n# Validation\nif ([string]::IsNullOrWhiteSpace($ProjectName)) {\n throw \"Required parameter Bitwarden.SecretsManager.RetrieveSecrets.ProjectName not specified.\"\n}\nif ([string]::IsNullOrWhiteSpace($BwsServerUrl)) {\n throw \"Required parameter Bitwarden.SecretsManager.RetrieveSecrets.ServerURL not specified.\"\n}\nif ([string]::IsNullOrWhiteSpace($BwsAccessToken)) {\n throw \"Required parameter Bitwarden.SecretsManager.RetrieveSecrets.AccessToken not specified.\"\n}\nif ([string]::IsNullOrWhiteSpace($SecretNames)) {\n throw \"Required parameter Bitwarden.SecretsManager.RetrieveSecrets.SecretNames not specified.\"\n}\n\n# Functions\nfunction Save-OctopusVariable {\n Param(\n [string] $name,\n [string] $value\n )\n if ($script:storedVariables -icontains $name) {\n Write-Warning \"A variable with name '$name' has already been created. Check your secret name parameters as this will likely cause unexpected behavior and should be investigated.\"\n }\n Set-OctopusVariable -Name $name -Value $value -Sensitive\n $script:storedVariables += $name\n\n if ($PrintVariableNames -eq $True) {\n Write-Output \"Created output variable: ##{Octopus.Action[$StepName].Output.$name}\"\n }\n}\n\nfunction Get-BwsProjectIdByName {\n param(\n [Parameter(Mandatory = $true)]\n [string]$Name,\n [Parameter(Mandatory = $true)]\n [string]$AccessToken\n )\n \n # 1. API Call: Retrieve all projects in JSON format (1st API Call)\n $ProjectJson = bws project list `\n --access-token $AccessToken `\n --server-url $BwsServerUrl `\n --output json | Out-String\n\n # 2. Convert to PowerShell objects and filter by name\n $Projects = $ProjectJson | ConvertFrom-Json\n\n # 3. Find the ID of the matching project\n $ProjectObject = $Projects | Where-Object { $_.name -eq $Name }\n\n if (-not $ProjectObject) {\n throw \"Error: Project '$Name' not found.\"\n }\n\n # Handle the case where the project name might not be unique\n if ($ProjectObject.Count -gt 1) {\n Write-Warning \"Multiple projects found with name '$Name'. Using the first ID found.\"\n }\n\n # Return the ID\n return $ProjectObject.id\n}\n\n# End Functions\n\n$script:storedVariables = @()\n$StepName = $OctopusParameters[\"Octopus.Step.Name\"]\n$Secrets = @()\n\n# Extract secret names\n@(($SecretNames -Split \"`n\").Trim()) | ForEach-Object {\n if (![string]::IsNullOrWhiteSpace($_)) {\n Write-Verbose \"Working on: '$_'\"\n $secretDefinition = ($_ -Split \"\\|\")\n $secretName = $secretDefinition[0].Trim()\n \n if ([string]::IsNullOrWhiteSpace($secretName)) {\n throw \"Unable to establish secret name from: '$($_)'\"\n }\n $secret = [PsCustomObject]@{\n Name = $secretName\n VariableName = if ($secretDefinition.Count -gt 1 -and ![string]::IsNullOrWhiteSpace($secretDefinition[1])) { $secretDefinition[1].Trim() } else { $secretName } # If VariableName is blank, use SecretName\n }\n $Secrets += $secret\n }\n}\n\nWrite-Verbose \"Project Name: $ProjectName\"\nWrite-Verbose \"Secrets to retrieve: $($Secrets.Count)\"\nWrite-Verbose \"Print variables: $PrintVariableNames\"\n\ntry {\n\n # 1. Get the Project ID from the friendly name\n Write-Output \"Looking up project ID for '$ProjectName'\"\n $ProjectID = Get-BwsProjectIdByName -Name $ProjectName -AccessToken $BwsAccessToken\n \n Write-Output \"Project ID found: $ProjectID\"\n \n # 2. Retrieve all secrets from the found project (The single efficient call)\n Write-Output \"Fetching all secrets from project.\"\n $SecretNamesToQuery = @($Secrets | Select-Object -ExpandProperty Name)\n\n # Use the projectId to get all secrets in that project\n $SecretsJson = bws secret list $ProjectID `\n --access-token $BwsAccessToken `\n --server-url $BwsServerUrl `\n --output json | Out-String\n $AllSecrets = $SecretsJson | ConvertFrom-Json\n\n # 3. Filter the local objects to only include the desired secret names\n Write-Output \"Filtering for desired secrets: $($SecretNamesToQuery -join ', ').\"\n $FilteredSecrets = $AllSecrets | Where-Object { $_.key -in $SecretNamesToQuery } | Select-Object -Property key, value\n \n foreach ($secret in $FilteredSecrets) {\n # Find the VariableName associated with the secret key\n $variableName = ($Secrets | Where-Object { $_.Name -eq $secret.key }).VariableName \n \n # Save the secret value to the output variable\n Save-OctopusVariable -name $variableName -value $secret.value\n }\n}\ncatch {\n throw \"An error occurred while retrieving secrets: $($_.Exception.Message)\"\n}\n\nWrite-Output \"Created $($script:storedVariables.Count) output variables\"" + }, + "Parameters": [ + { + "Id": "2a500677-eb3e-4e1c-93bd-0fa896aad9fd", + "Name": "Bitwarden.SecretsManager.RetrieveSecrets.ServerUrl", + "Label": "Server Url", + "HelpText": "Provide the Server Url for retrieving secrets. Default: `https://vault.bitwarden.eu`", + "DefaultValue": "https://vault.bitwarden.eu", + "DisplaySettings": { + "Octopus.ControlType": "SingleLineText" + } + }, + { + "Id": "18b7321d-803e-4217-993d-6416dd6eb5f7", + "Name": "Bitwarden.SecretsManager.RetrieveSecrets.ProjectName", + "Label": "Project Name", + "HelpText": "Provide the name of the project from which to retrieve secrets.", + "DefaultValue": "", + "DisplaySettings": { + "Octopus.ControlType": "SingleLineText" + } + }, + { + "Id": "0dada9ac-3a35-4215-b03f-9024486ee7a2", + "Name": "Bitwarden.SecretsManager.RetrieveSecrets.AccessToken", + "Label": "Machine Account Access Token", + "HelpText": "Provide the machine account [access token](https://bitwarden.com/help/access-tokens/) used to authenticate to retrieve secrets.", + "DefaultValue": "", + "DisplaySettings": { + "Octopus.ControlType": "Sensitive" + } + }, + { + "Id": "b10f61d8-dedc-4a91-add3-451de0cfd47d", + "Name": "Bitwarden.SecretsManager.RetrieveSecrets.SecretNames", + "Label": "Secret names to retrieve", + "HelpText": "Specify the names of the secrets to be returned from Secret Manager in Google Cloud, in the format:\n\n`SecretName | OutputVariableName` where:\n\n- `SecretName` is the name of the secret to retrieve.\n- `OutputVariableName` is the _optional_ Octopus [output variable](https://octopus.com/docs/projects/variables/output-variables) name to store the secret's value in. *If this value isn't specified, an output name will be generated dynamically*.\n\n**Note:** Multiple fields can be retrieved by entering each one on a new line.", + "DefaultValue": "", + "DisplaySettings": { + "Octopus.ControlType": "MultiLineText" + } + }, + { + "Id": "0a98e37e-7907-4e49-919a-5e50c7765469", + "Name": "Bitwarden.SecretsManager.RetrieveSecrets.PrintVariableNames", + "Label": "Print output variable names", + "HelpText": "Write out the names of the Octopus [output variables](https://octopus.com/docs/projects/variables/output-variables) in the task log. Default: `False`.", + "DefaultValue": "False", + "DisplaySettings": { + "Octopus.ControlType": "Checkbox" + } + } + ], + "StepPackageId": "Octopus.Script", + "$Meta": { + "ExportedAt": "2025-10-09T16:22:38.417Z", + "OctopusVersion": "2025.4.3435", + "Type": "ActionTemplate" + }, + "LastModifiedBy": "harrisonmeister", + "Category": "bitwarden" +} diff --git a/step-templates/logos/bitwarden.png b/step-templates/logos/bitwarden.png new file mode 100644 index 0000000000000000000000000000000000000000..5e40cb25e6daab4210729c3a4724bd11c471093f GIT binary patch literal 10512 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 QK)^>+_4yN&@~hDQ1$gklN&o-= literal 0 HcmV?d00001