diff --git a/.gitea/workflows/publish-psgallery.yml b/.gitea/workflows/publish-psgallery.yml new file mode 100644 index 0000000..bb33266 --- /dev/null +++ b/.gitea/workflows/publish-psgallery.yml @@ -0,0 +1,47 @@ +name: Publish to PowerShell Gallery + +on: + pull_request: + types: [closed] + branches: [main] + +jobs: + publish: + if: github.event.pull_request.merged == true + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' + + - name: Build and test module + shell: pwsh + run: | + ./build.ps1 -RunTests + + - name: Verify PowerShell Gallery API key is configured + shell: pwsh + env: + PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }} + run: | + if ([string]::IsNullOrWhiteSpace($env:PSGALLERY_API_KEY)) { + throw "Repository secret 'PSGALLERY_API_KEY' is not configured." + } + + - name: Publish to PowerShell Gallery + shell: pwsh + env: + PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }} + run: | + $moduleDir = Join-Path $PWD 'Module/PSInfisicalAPI' + Write-Host "Publishing module from: $moduleDir" + Publish-Module ` + -Path $moduleDir ` + -NuGetApiKey $env:PSGALLERY_API_KEY ` + -Verbose diff --git a/.gitignore b/.gitignore index ed32bf6..8968d26 100644 --- a/.gitignore +++ b/.gitignore @@ -4,8 +4,9 @@ obj/ Artifacts/ Releases/ -## Module bin output is generated by build.ps1 -Module/PSInfisicalAPI/bin/ +## Module bin output is generated by build.ps1, but tracked so the module is consumable from source +!Module/PSInfisicalAPI/bin/ +!Module/PSInfisicalAPI/bin/** ## VS / Rider / VSCode .vs/ diff --git a/CHANGELOG.md b/CHANGELOG.md index beecad1..97db1a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,23 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) loos ## Unreleased +## 2026.06.02.1648 + +- Build produced from commit 430e3a00c921. + +## Unreleased (carried forward) + ## 2026.06.02.1638 - Build produced from commit 3c47d6ff30ec. -## Unreleased (carried forward) +## Unreleased + +## 2026.06.02.1648 + +- Build produced from commit 430e3a00c921. + +## Unreleased (carried forward) (carried forward) ## 2026.06.02.1611 @@ -18,11 +30,23 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) loos ## Unreleased +## 2026.06.02.1648 + +- Build produced from commit 430e3a00c921. + +## Unreleased (carried forward) + ## 2026.06.02.1638 - Build produced from commit 3c47d6ff30ec. -## Unreleased (carried forward) (carried forward) +## Unreleased + +## 2026.06.02.1648 + +- Build produced from commit 430e3a00c921. + +## Unreleased (carried forward) (carried forward) (carried forward) ### Added diff --git a/Module/PSInfisicalAPI/PSInfisicalAPI.psd1 b/Module/PSInfisicalAPI/PSInfisicalAPI.psd1 index 1b52c29..19a18ea 100644 --- a/Module/PSInfisicalAPI/PSInfisicalAPI.psd1 +++ b/Module/PSInfisicalAPI/PSInfisicalAPI.psd1 @@ -1,11 +1,11 @@ @{ RootModule = 'PSInfisicalAPI.psm1' - ModuleVersion = '2026.06.02.1638' + ModuleVersion = '2026.06.02.1648' GUID = 'b8a2f3d4-7c51-4d2f-9e6a-1f0c8b3d4e51' - Author = 'Alphaeus Mote' - CompanyName = '' - Copyright = '(c) Alphaeus Mote. All rights reserved.' - Description = 'PSInfisicalAPI is a C# binary PowerShell module for the Infisical REST API.' + Author = 'Grace Solutions' + CompanyName = 'Grace Solutions' + Copyright = '(c) Grace Solutions. All rights reserved.' + Description = 'PSInfisicalAPI is a C# binary PowerShell module for the Infisical REST API, providing cmdlets for authentication, secret retrieval, and export with automatic environment-variable discovery across Process, User, and Machine scopes.' PowerShellVersion = '5.1' CompatiblePSEditions = @('Desktop','Core') FunctionsToExport = @() @@ -23,10 +23,11 @@ TypesToProcess = @('PSInfisicalAPI.Types.ps1xml') PrivateData = @{ PSData = @{ - Tags = @('Infisical','Secrets','API','SecureString') - ProjectUri = '' - ReleaseNotes = '' - CommitHash = '3c47d6ff30ec' + Tags = @('Infisical','Secrets','API','SecureString','Vault','Authentication') + LicenseUri = 'https://www.gnu.org/licenses/agpl-3.0.html' + ProjectUri = 'https://prod.git.gracesolution.info/gsadmin/PSInfisicalAPI' + ReleaseNotes = 'See CHANGELOG.md in the project repository for release history.' + CommitHash = '430e3a00c921' } } } \ No newline at end of file diff --git a/Module/PSInfisicalAPI/bin/Newtonsoft.Json.dll b/Module/PSInfisicalAPI/bin/Newtonsoft.Json.dll new file mode 100644 index 0000000..3af21d5 Binary files /dev/null and b/Module/PSInfisicalAPI/bin/Newtonsoft.Json.dll differ diff --git a/Module/PSInfisicalAPI/bin/PSInfisicalAPI.dll b/Module/PSInfisicalAPI/bin/PSInfisicalAPI.dll new file mode 100644 index 0000000..aba2d23 Binary files /dev/null and b/Module/PSInfisicalAPI/bin/PSInfisicalAPI.dll differ diff --git a/Module/PSInfisicalAPI/bin/YamlDotNet.dll b/Module/PSInfisicalAPI/bin/YamlDotNet.dll new file mode 100644 index 0000000..f9abf6c Binary files /dev/null and b/Module/PSInfisicalAPI/bin/YamlDotNet.dll differ diff --git a/Module/PSInfisicalAPI/en-US/about_PSInfisicalAPI.help.txt b/Module/PSInfisicalAPI/en-US/about_PSInfisicalAPI.help.txt new file mode 100644 index 0000000..ac9ffea --- /dev/null +++ b/Module/PSInfisicalAPI/en-US/about_PSInfisicalAPI.help.txt @@ -0,0 +1,97 @@ +TOPIC + about_PSInfisicalAPI + +SHORT DESCRIPTION + PSInfisicalAPI is a C# binary PowerShell module that exposes cmdlets for + authenticating against and retrieving secrets from the Infisical REST API, + with automatic environment-variable discovery for connection parameters. + +LONG DESCRIPTION + The module provides the following cmdlets: + + Connect-Infisical Establish a session. + Disconnect-Infisical Clear the current session. + Get-InfisicalSecrets List secrets at a path. + Get-InfisicalSecret Retrieve a single secret by name. + ConvertTo-InfisicalSecretDictionary Convert secret objects to a hashtable. + Export-InfisicalSecrets Export secrets to JSON, YAML, XML, or .env. + + Use Get-Help -Full for parameter details. + +AUTHENTICATION + Connect-Infisical supports two parameter sets: + + UniversalAuth -ClientId / -ClientSecret (SecureString) + Token -AccessToken (SecureString) + + Common parameters apply to both sets: + -BaseUri, -OrganizationId, -ProjectId, -Environment, + -SecretPath (default '/'), -ApiVersion (default 'v4'), -PassThru. + +ENVIRONMENT VARIABLE DISCOVERY + When any of the Connect-Infisical parameters are omitted, null, or contain + only whitespace, the cmdlet scans environment variables in three scopes, + in order: + + 1. Process + 2. User + 3. Machine + + The first matching variable with a non-blank value is used. Explicitly + supplied parameter values always win. + + Patterns are case-insensitive and match Infisical's CLI defaults plus + common variants such as CLOUDINIT_INFISICAL_* and custom-prefixed names + (for example "myapp_infisical_client_id"). + + Parameter Example variable names + ---------------- ---------------------------------------------------------- + BaseUri INFISICAL_API_URL, INFISICAL_BASE_URL, INFISICAL_HOST + OrganizationId INFISICAL_ORG_ID, INFISICAL_ORGANIZATION_ID + ProjectId INFISICAL_PROJECT_ID, INFISICAL_WORKSPACE_ID + Environment INFISICAL_ENVIRONMENT, INFISICAL_ENV, INFISICAL_ENV_SLUG + ClientId INFISICAL_CLIENT_ID, INFISICAL_UNIVERSAL_AUTH_CLIENT_ID + ClientSecret INFISICAL_CLIENT_SECRET, INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET + AccessToken INFISICAL_TOKEN, INFISICAL_ACCESS_TOKEN, INFISICAL_AUTH_TOKEN + SecretPath INFISICAL_SECRET_PATH, INFISICAL_DEFAULT_SECRET_PATH + ApiVersion INFISICAL_API_VERSION + + Sensitive values (ClientSecret, AccessToken) are read directly into a + read-only SecureString. Discovered values are never written to logs; + only the variable name and the scope it was found in are recorded. + + Use -Verbose to see the scan announcement and any discovered variable + names. + +EXAMPLES + Example 1: Zero-configuration connect (all values from environment). + + Connect-Infisical + Get-InfisicalSecrets + + Example 2: Explicit parameters override discovery. + + $secret = Read-Host -AsSecureString 'Client Secret' + Connect-Infisical -Environment prod -ClientSecret $secret + + Example 3: Token-based authentication. + + $token = Read-Host -AsSecureString 'Access Token' + Connect-Infisical -AccessToken $token + + Example 4: Export to a .env file. + + Get-InfisicalSecrets | + Export-InfisicalSecrets -Path .\secrets.env -Format Env + +SECURITY NOTES + - SecureString is used for ClientSecret, AccessToken, and any secret + payloads returned by the API. + - Sanitization is applied before any value reaches the logging pipeline. + - Sessions are stored in process-local state only and never persisted. + +SEE ALSO + Get-Help Connect-Infisical -Full + Get-Help Get-InfisicalSecrets -Full + Get-Help Export-InfisicalSecrets -Full + https://infisical.com/docs/api-reference/overview/introduction diff --git a/README.md b/README.md index c37544b..835c2b5 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,128 @@ # PSInfisicalAPI +A C# binary PowerShell module for interacting with the [Infisical](https://infisical.com/) REST API. It provides cmdlets for authentication, secret retrieval, structured export, and includes automatic environment-variable discovery so connections can be established with little or no inline configuration. + +- License: AGPL-3.0 +- Author: Grace Solutions +- Target framework: .NET Standard 2.0 (compatible with Windows PowerShell 5.1 and PowerShell 7+) + +## Installation + +### From the PowerShell Gallery + +```powershell +Install-Module -Name PSInfisicalAPI -Scope CurrentUser +Import-Module -Name PSInfisicalAPI +``` + +### From source + +```powershell +git clone https://prod.git.gracesolution.info/gsadmin/PSInfisicalAPI.git +cd PSInfisicalAPI +pwsh -NoProfile -ExecutionPolicy Bypass -File .\build.ps1 -RunTests +Import-Module -Name .\Module\PSInfisicalAPI +``` + +## Cmdlets + +| Cmdlet | Purpose | +| ------------------------------------- | -------------------------------------------------------------------------- | +| `Connect-Infisical` | Establish a session using Universal Auth or a pre-issued access token. | +| `Disconnect-Infisical` | Clear the current session. | +| `Get-InfisicalSecrets` | List secrets at a given path / environment. | +| `Get-InfisicalSecret` | Retrieve a single secret by name. | +| `ConvertTo-InfisicalSecretDictionary` | Convert secret objects into a `Hashtable` keyed by `SecretKey`. | +| `Export-InfisicalSecrets` | Export secrets to JSON, YAML, XML, or `.env` format. | + +Use `Get-Help -Full` for parameter details and `Get-Help about_PSInfisicalAPI` for the module overview. + +## Quick start + +```powershell +$secureSecret = Read-Host -AsSecureString 'Client Secret' + +$connection = Connect-Infisical ` + -BaseUri 'https://app.infisical.com' ` + -OrganizationId '00000000-0000-0000-0000-000000000000' ` + -ProjectId '11111111-1111-1111-1111-111111111111' ` + -Environment 'dev' ` + -ClientId 'machine-identity-client-id' ` + -ClientSecret $secureSecret ` + -PassThru + +Get-InfisicalSecrets -SecretPath '/' +Disconnect-Infisical +``` + +## Automatic environment-variable discovery + +When `Connect-Infisical` is invoked with one or more parameters missing (or set to whitespace/empty), the cmdlet searches environment variables and uses the first value it finds. This makes invocation as simple as `Connect-Infisical` when variables are set up in advance. + +### Scope precedence + +Scopes are searched in order; the first matching variable with a non-blank value wins: + +1. `Process` +2. `User` +3. `Machine` + +### Patterns + +The resolver matches case-insensitively against patterns aligned with Infisical's CLI defaults plus common variants such as `CLOUDINIT_INFISICAL_*` and custom-prefixed names (e.g., `myapp_infisical_client_id`). + +| Parameter | Example variable names matched | +| ----------------- | ------------------------------------------------------------------------------------ | +| `BaseUri` | `INFISICAL_API_URL`, `INFISICAL_BASE_URL`, `INFISICAL_HOST` | +| `OrganizationId` | `INFISICAL_ORG_ID`, `INFISICAL_ORGANIZATION_ID` | +| `ProjectId` | `INFISICAL_PROJECT_ID`, `INFISICAL_WORKSPACE_ID` | +| `Environment` | `INFISICAL_ENVIRONMENT`, `INFISICAL_ENV`, `INFISICAL_ENV_SLUG` | +| `ClientId` | `INFISICAL_CLIENT_ID`, `INFISICAL_UNIVERSAL_AUTH_CLIENT_ID` | +| `ClientSecret` | `INFISICAL_CLIENT_SECRET`, `INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET` | +| `AccessToken` | `INFISICAL_TOKEN`, `INFISICAL_ACCESS_TOKEN`, `INFISICAL_AUTH_TOKEN` | +| `SecretPath` | `INFISICAL_SECRET_PATH`, `INFISICAL_DEFAULT_SECRET_PATH` | +| `ApiVersion` | `INFISICAL_API_VERSION` | + +Sensitive values (`ClientSecret`, `AccessToken`) are read directly into a read-only `SecureString` and never logged. + +### Zero-configuration example + +```powershell +[Environment]::SetEnvironmentVariable('INFISICAL_API_URL', 'https://app.infisical.com', 'User') +[Environment]::SetEnvironmentVariable('INFISICAL_ORG_ID', '00000000-0000-0000-0000-000000000000', 'User') +[Environment]::SetEnvironmentVariable('INFISICAL_PROJECT_ID', '11111111-1111-1111-1111-111111111111', 'User') +[Environment]::SetEnvironmentVariable('INFISICAL_ENVIRONMENT', 'dev', 'User') +[Environment]::SetEnvironmentVariable('INFISICAL_CLIENT_ID', 'machine-identity-client-id', 'User') +[Environment]::SetEnvironmentVariable('INFISICAL_CLIENT_SECRET', 'super-secret-value', 'User') + +Connect-Infisical +Get-InfisicalSecrets +``` + +### Mixed example (explicit values override discovery) + +Explicit parameters always win over discovered values; blank/whitespace explicit values trigger discovery. + +```powershell +Connect-Infisical -Environment 'prod' # everything else discovered from environment +``` + +### Logging + +The resolver emits a single verbose line announcing the scan and one informational line per discovered variable (variable name and scope; values are never logged). Use `-Verbose` to see the scan announcement. + +## Building + +```powershell +pwsh -NoProfile -ExecutionPolicy Bypass -File .\build.ps1 -RunTests +``` + +The script builds the binary, runs unit tests, publishes binaries into `Module/PSInfisicalAPI/bin/`, regenerates the manifest, and validates that the module imports. + +## Continuous integration + +`.gitea/workflows/publish-psgallery.yml` publishes the module to the PowerShell Gallery whenever a pull request is merged into `main`. The workflow expects a repository secret named `PSGALLERY_API_KEY` containing a valid Gallery API key. + +## License + +Distributed under the GNU Affero General Public License v3.0. See [LICENSE](LICENSE). diff --git a/build.ps1 b/build.ps1 index 1bc4ae9..9fb8590 100644 --- a/build.ps1 +++ b/build.ps1 @@ -90,10 +90,10 @@ function Write-Manifest { RootModule = 'PSInfisicalAPI.psm1' ModuleVersion = '$ModuleVersion' GUID = '$ModuleGuid' - Author = 'Alphaeus Mote' - CompanyName = '' - Copyright = '(c) Alphaeus Mote. All rights reserved.' - Description = 'PSInfisicalAPI is a C# binary PowerShell module for the Infisical REST API.' + Author = 'Grace Solutions' + CompanyName = 'Grace Solutions' + Copyright = '(c) Grace Solutions. All rights reserved.' + Description = 'PSInfisicalAPI is a C# binary PowerShell module for the Infisical REST API, providing cmdlets for authentication, secret retrieval, and export with automatic environment-variable discovery across Process, User, and Machine scopes.' PowerShellVersion = '5.1' CompatiblePSEditions = @('Desktop','Core') FunctionsToExport = @() @@ -111,10 +111,11 @@ function Write-Manifest { TypesToProcess = @('PSInfisicalAPI.Types.ps1xml') PrivateData = @{ PSData = @{ - Tags = @('Infisical','Secrets','API','SecureString') - ProjectUri = '' - ReleaseNotes = '' - CommitHash = '$CommitHash' + Tags = @('Infisical','Secrets','API','SecureString','Vault','Authentication') + LicenseUri = 'https://www.gnu.org/licenses/agpl-3.0.html' + ProjectUri = 'https://prod.git.gracesolution.info/gsadmin/PSInfisicalAPI' + ReleaseNotes = 'See CHANGELOG.md in the project repository for release history.' + CommitHash = '$CommitHash' } } } diff --git a/docs/DesignSpec.md b/docs/DesignSpec.md index c7fa7af..994d095 100644 --- a/docs/DesignSpec.md +++ b/docs/DesignSpec.md @@ -187,7 +187,7 @@ Example shape: RootModule = 'PSInfisicalAPI.psm1' ModuleVersion = 'yyyy.MM.dd.HHmm' GUID = '' - Author = 'Alphaeus Mote' + Author = 'Grace Solutions' CompanyName = '' Copyright = '' PowerShellVersion = '5.1' diff --git a/src/PSInfisicalAPI/PSInfisicalAPI.csproj b/src/PSInfisicalAPI/PSInfisicalAPI.csproj index 896fcdf..8c659ac 100644 --- a/src/PSInfisicalAPI/PSInfisicalAPI.csproj +++ b/src/PSInfisicalAPI/PSInfisicalAPI.csproj @@ -12,8 +12,8 @@ $(BuildAssemblyVersion) $(BuildAssemblyVersion) $(BuildVersion) - - Alphaeus Mote + Grace Solutions + Grace Solutions PSInfisicalAPI true false