CI: add dotnet --info / df -h / free -m diagnostics and an explicit 'Restore NuGet packages' step before build to isolate restore failures (build of e15f650 on main exited with code -1 and zero dotnet output). #5

Merged
gsadmin merged 27 commits from dev into main 2026-06-05 01:24:51 +00:00
94 changed files with 9073 additions and 750 deletions
+17
View File
@@ -27,6 +27,23 @@ jobs:
}
Write-Host ("pwsh: " + (pwsh -NoProfile -Command '$PSVersionTable.PSVersion.ToString()'))
Write-Host ("dotnet: " + (dotnet --version))
Write-Host '--- dotnet --info ---'
dotnet --info
Write-Host '--- disk free ---'
df -h .
Write-Host '--- memory ---'
free -m
- name: Restore NuGet packages
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
Write-Host '==> dotnet restore src/PSInfisicalAPI/PSInfisicalAPI.csproj'
dotnet restore src/PSInfisicalAPI/PSInfisicalAPI.csproj --verbosity normal
if ($LASTEXITCODE -ne 0) { throw "Restore of PSInfisicalAPI.csproj failed with exit code $LASTEXITCODE" }
Write-Host '==> dotnet restore src/PSInfisicalAPI.Tests/PSInfisicalAPI.Tests.csproj'
dotnet restore src/PSInfisicalAPI.Tests/PSInfisicalAPI.Tests.csproj --verbosity normal
if ($LASTEXITCODE -ne 0) { throw "Restore of PSInfisicalAPI.Tests.csproj failed with exit code $LASTEXITCODE" }
- name: Build and test module
shell: pwsh
+195 -4
View File
@@ -6,7 +6,198 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) loos
## Unreleased
- **CI — Gitea artifact upload fix**: Replaced `actions/upload-artifact@v4` and `actions/download-artifact@v4` with the Gitea-compatible forks `christopherhx/gitea-upload-artifact@v4` and `christopherhx/gitea-download-artifact@v4` in `.gitea/workflows/publish-psgallery.yml`. The upstream v4 actions abort on Gitea because Gitea is detected as GHES, which the upstream v4 actions do not support (see [go-gitea/gitea#28853](https://github.com/go-gitea/gitea/issues/28853)).
## 2026.06.05.0117
- Build produced from commit cffda99591c9.
## Unreleased (carried forward)
## 2026.06.05.0015
- Build produced from commit fb27ab8a8503.
## Unreleased (carried forward)
- Fixed `ParameterNameConflictsWithAlias` registration error on `Get-InfisicalCertificateApplication`, `Get-InfisicalCertificateApplicationEnrollment`, and `New-InfisicalScepDynamicChallenge`. The cmdlets each declared an `[Alias]` entry that matched the parameter's own name, which PowerShell rejects at bind time and made the cmdlets unusable.
## 2026.06.04.2335
- Build produced from commit 3c39a99b9a4c.
## Unreleased (carried forward)
## 2026.06.04.2305
- Build produced from commit 485ee8a7dd6a.
## Unreleased (carried forward)
- `Get-InfisicalCertificateApplication` added with `List` (default), `ById`, and `ByName` parameter sets. Binds to `/api/v1/cert-manager/applications` (list) and `/api/v1/cert-manager/applications/{applicationId}` / `/by-name/{name}` for single retrieval. Requests carry the `x-infisical-project-id` header so the certificate-manager scope resolves correctly. New `InfisicalCertificateApplication` model surfaces id, project, name, description, and counts.
- `Get-InfisicalCertificateApplicationEnrollment` added. Returns the API/EST/ACME/SCEP enrollment configuration for an application/profile pair (`GET /api/v1/cert-manager/applications/{applicationId}/profiles/{profileId}/enrollment`). The new `InfisicalCertificateApplicationEnrollment` model includes sub-blocks for each enrollment protocol; the SCEP block computes a SHA-1 `RaCertificateThumbprint` from the RA certificate PEM so it can be fed directly into MDM payloads.
- `New-InfisicalScepDynamicChallenge` added. Wraps `POST /scep/applications/{applicationId}/profiles/{profileId}/challenge` and returns the minted challenge as a `SecureString` (default) or string (`-AsPlainText`). The endpoint is gated by the dynamic-challenge feature on the target Infisical instance and by the calling identity's permission on `certificate-application-enrollment`.
- `Get-InfisicalScepMdmProfile` reworked into three parameter sets. `FromEnrollment` (new default) consumes an `InfisicalCertificateApplicationEnrollment` and auto-resolves `ServerUrl` from `scep.scepEndpointUrl`, `CAThumbprint` from the RA certificate, and the SCEP challenge (auto-minting when `challengeType=dynamic` and `-Challenge` is not supplied). `FromProfile` keeps the legacy projection from an `InfisicalCertificateProfile`, now requires `-ApplicationId`, and the default server URL is built against `/scep/applications/{appId}/profiles/{profileId}/pkiclient.exe`. `Manual` requires explicit `-ServerUrl`, `-Challenge`, and `-UniqueId`.
- `InfisicalApiInvoker` accepts an optional `extraHeaders` argument so callers can attach the `x-infisical-project-id` header and override `Accept` for plain-text responses (used by the new SCEP challenge endpoint).
## 2026.06.04.2147
- Build produced from commit 183fb48c32ce.
## Unreleased (carried forward)
- `Get-InfisicalScepMdmProfile` added. Projects an `InfisicalCertificateProfile` (pipeline-bound) into a new `InfisicalScepMdmProfile` model that mirrors the Windows `ClientCertificateInstall/SCEP` CSP node set. `-ServerUrl` defaults to `{baseUri}/scep/{profileId}/pkiclient.exe` derived from the active connection (the `pkiclient.exe` suffix is the RFC 8894 / Cisco SCEP client compatibility holdover, not a server-side executable). `-UniqueId` defaults to a sanitized slug. `-Challenge` is a `SecureString` decrypted only when materializing the model. `KeyAlgorithm` and `EkuMapping` are inherited from the source profile defaults unless overridden.
- `Export-InfisicalScepMdmProfile` added. Serializes the model via `InfisicalScepMdmProfile.ToSyncMl()` (XDocument build, XmlWriter emit, XmlReader round-trip validation) and writes the result to `-Path` as UTF-8 without BOM. Auto-creates the target directory, honors `-WhatIf`/`-Confirm`, and follows the project rule for `-Force`: if the destination exists without `-Force`, the cmdlet logs a warning and returns instead of throwing. `-PassThru` emits the resulting `FileInfo`.
- `Write-InfisicalScepMdmProfileToWmi` added. Submits the same model to the local MDM Bridge WMI provider by invoking `New-CimInstance -Namespace root/cimv2/mdm/dmmap -ClassName MDM_ClientCertificateInstall_SCEP02 -Property <hashtable>` through the host runspace (no new package references). Guards: throws `PlatformNotSupportedException` off Windows; device-scope enrollment requires an elevated session unless `-SkipElevationCheck` is passed; supports `-WhatIf`/`-Confirm`; `-PassThru` emits the returned CIM instance. Override `-ClassName` when targeting a different SCEP CSP version on the host.
## 2026.06.04.2112
- Build produced from commit 3754de74f6c8.
## Unreleased (carried forward)
- Infisical API error responses are now parsed to surface the server-side `message`, `error`, and `reqId` fields. The 4xx/5xx exception message includes the human-readable explanation (e.g. "The project is of type secret-manager") instead of an opaque `Infisical API returned 400 (Bad Request)`. The `InfisicalApiException` gains `ApiErrorMessage` and `ApiRequestId` properties; `InfisicalErrorDetails` carries the same fields so PowerShell error records and logger output expose them.
- `Get-InfisicalCertificateProfile` added with `List` (default) and `ById` parameter sets. List binds to `GET /api/v1/cert-manager/certificate-profiles` (optional `-Limit`, `-Offset`, `-IncludeConfigs`); ById binds to `GET /api/v1/cert-manager/certificate-profiles/{certificateProfileId}`. New `InfisicalCertificateProfile` model surfaces ca/policy ids, slug, enrollment type, per-profile defaults (ttl, key/extended key usages), and the embedded CA/policy/apiConfig summaries.
- `Get-InfisicalCertificatePolicy` added with `List` (default) and `ById` parameter sets. List binds to `GET /api/v1/cert-manager/certificate-policies` (optional `-Limit`, `-Offset`); ById binds to `GET /api/v1/cert-manager/certificate-policies/{certificatePolicyId}`. New `InfisicalCertificatePolicy` model surfaces subject, SANs, key usages, extended key usages, algorithms, and validity. Polymorphic string-or-array fields (`allowed`, `required`, `keyAlgorithm`) are normalized to arrays; `sans` is normalized whether the API returns an object or an array.
- `Get-InfisicalCertificateAuthority` gains a `-Kind` parameter on the List parameter set with values `Internal` (default, preserves prior behavior against `/api/v1/cert-manager/ca/internal`), `Any` (binds to the generic `/api/v1/cert-manager/ca` endpoint which returns both internal and ACME CAs), and `Acme` (uses the generic endpoint and client-side filters to ACME issuers only). ById retrieval is unchanged and still resolves against the internal CA endpoint.
- `Request-InfisicalCertificate` gains a `ByProfile` parameter set bound by the new `-CertificateProfileId` parameter (alias `ProfileId`). The cmdlet generates a local keypair and CSR as usual, then POSTs to `/api/v1/cert-manager/certificates` with the profile id, the CSR, and a subject/attribute envelope (commonName, organization, organizationalUnit, country, state, locality, ttl, notBefore, notAfter, keyUsages, extendedKeyUsages). The wrapped response (`{certificate:{certificate,certificateChain,issuingCaCertificate,serialNumber,certificateId,privateKey}, certificateRequestId, status, message}`) is unwrapped into the existing `InfisicalSignedCertificate` shape so the install / reuse / chain-completion paths continue to work unchanged. Issuance that returns without a certificate body (e.g. status `pending_approval` or `pending_validation`) is logged as a warning and the cmdlet emits a status-only `InfisicalCertificateResult` (new `Status`, `StatusMessage`, `CertificateRequestId` properties) instead of throwing; install / chain / private-key-write steps are skipped in that case. Whether issuance is immediate or pending is dictated by the certificate policy bound to the profile (auto-approve vs. manual review and any required validation).
## 2026.06.04.1920
- Build produced from commit 0f8f44afdb38.
## Unreleased (carried forward)
- `build.ps1` gains a `-CommitArtifacts` switch that, after a successful build, stages and commits only the build outputs (`Module/PSInfisicalAPI/bin/**`, `Module/PSInfisicalAPI/PSInfisicalAPI.psd1`, and the auto-inserted `CHANGELOG.md` build stamp) with a message that references the source commit whose hash is now embedded in `BuildCommitHash`. The switch is mutually exclusive with the older broader `-CommitOnSuccess` (which still uses `git add -A`). README extended with a "Committing source and build artifacts in lockstep" section describing the recommended two-commit workflow.
## 2026.06.04.1917
- Build produced from commit a34db831d8bf.
## Unreleased (carried forward)
## 2026.06.04.1915
- Build produced from commit 2489b7adca98.
## Unreleased (carried forward)
## 2026.06.04.1911
- Build produced from commit 51bf819c37e5.
## Unreleased (carried forward)
## 2026.06.04.1906
- Build produced from commit 51bf819c37e5.
## Unreleased (carried forward)
- **BREAKING**: Removed the plural-noun discovery cmdlets `Get-InfisicalProjects`, `Get-InfisicalEnvironments`, `Get-InfisicalFolders`, `Get-InfisicalTags`, `Get-InfisicalSecrets`, and `Get-InfisicalCertificates`. Their behavior is now folded into the corresponding singular cmdlets via a `List` (default) / single-record parameter set pair, matching the existing `Get-InfisicalCertificateAuthority` precedent. Callers should drop the trailing `s`; invocation without the identity parameter (`-ProjectId`, `-EnvironmentSlugOrId`, `-FolderNameOrId`, `-TagSlugOrId`, `-SecretName`, `-SerialNumber`) now returns the list, and supplying the identity parameter returns the single record. No back-compat aliases were added.
- Added `Get-InfisicalPkiSubscriber` with `List` (default) and `ByName` parameter sets, backed by new `InfisicalPkiClient.ListPkiSubscribers` and `GetPkiSubscriber` methods, an `InfisicalPkiSubscriber` model, and corresponding DTOs/mapper. Use the emitted `Name` (slug) on `Request-InfisicalCertificate -PkiSubscriberSlug`.
- **Bug fix**: `Request-InfisicalCertificate -PkiSubscriberSlug ...` was returning 404 because the registry's `SignCertificateBySubscriber` endpoint pointed at `/api/v1/pki/pki-subscribers/{subscriberName}/sign-certificate` and `/api/v1/cert-manager/pki-subscribers/...`. Per Infisical's `v1/index.ts`, the subscriber router is mounted at `/pki/subscribers`, so the single correct path is `/api/v1/pki/subscribers/{subscriberName}/sign-certificate`. The redundant `cert-manager` template was removed; the PKI endpoint registry tests were updated to match.
- Updated MAML help in `Module/PSInfisicalAPI/en-US/PSInfisicalAPI.dll-Help.xml`: the six consolidated cmdlets and the new `Get-InfisicalPkiSubscriber` each ship three examples — two straight-line invocations (one per parameter set) plus one `OrderedDictionary` splat example. All in-text references to the removed plural cmdlets across other cmdlets' examples were updated to the singular form.
- `build.ps1`: `CmdletsToExport` and the `Test-ModuleImports` expected cmdlet list were updated to drop the six plural cmdlets and add `Get-InfisicalPkiSubscriber` (total: 34 exported cmdlets).
## 2026.06.04.1825
- Build produced from commit 19615363e356.
## Unreleased (carried forward)
## 2026.06.04.1820
- Build produced from commit 19615363e356.
## Unreleased (carried forward)
- `Install-InfisicalCertificate` now routes chain certificates by self-signed status instead of dumping every chain entry into the Intermediate Certification Authorities store. Self-signed roots are installed into `StoreName.Root` (Trusted Root Certification Authorities) and non-self-signed intermediates are installed into `StoreName.CertificateAuthority` (Intermediate Certification Authorities). The leaf continues to use the user-specified `-StoreName`/`-StoreLocation` (default `My`/`CurrentUser`). `Request-InfisicalCertificate` already routed chain certs correctly; the same routing helper is now shared by both cmdlets.
- `InfisicalCertificateRequestHelpers` exposes a new public `GetChainCertificateTargetStore(X509Certificate2)` classifier and a new `InstallChain(IEnumerable<X509Certificate2>, StoreLocation, bool, IInfisicalLogger, string)` overload. The existing `InstallChain(InfisicalSignedCertificate, ...)` overload now delegates to the new collection-based overload, so PKI chain-installation routing is centralized in one place.
## 2026.06.04.1810
- Build produced from commit 19615363e356.
## Unreleased (carried forward)
- Authored MAML help (`Module/PSInfisicalAPI/en-US/PSInfisicalAPI.dll-Help.xml`) covering all 39 exported cmdlets. Every entry includes a synopsis, description, notes section, and two examples: a one-liner and an `OrderedDictionary` splat (with `OrdinalIgnoreCase`) that includes preceding `Get-` resolver commands wherever IDs or slugs are required.
- `build.ps1` now stages the cmdlet help XML next to the deployed binary. After the publish step, every culture directory under `Module/PSInfisicalAPI/` (matching `xx` or `xx-XX`) that contains `PSInfisicalAPI.dll-Help.xml` is mirrored into `bin/<culture>/`. The script hard-fails if `bin/en-US/PSInfisicalAPI.dll-Help.xml` is missing or contains zero `<command:command>` entries.
- `Test-ModuleImports` in `build.ps1` now dynamically enumerates exported cmdlets via `Get-Command -Module PSInfisicalAPI -CommandType Cmdlet`, cross-checks the result against an expected list of 39 cmdlet names (including the previously-missing `Copy-InfisicalSecret`), and for each cmdlet asserts that `Get-Help -Full` returns a non-empty synopsis (rejecting PowerShell's auto-generated cmdlet-name fallback), a non-empty description, and that `Get-Help -Examples` returns at least one example node whose `<dev:code>` block is non-empty.
## 2026.06.04.1808
- Build produced from commit 19615363e356.
## Unreleased (carried forward)
## 2026.06.04.1658
- Build produced from commit 19615363e356.
## Unreleased (carried forward)
- `Request-InfisicalCertificate` reuse path now falls back to the Infisical certificate-bundle endpoint when the local trust stores do not contain the issuing intermediates or root. The cmdlet builds the local chain first; if the result has no intermediates and no root, it fetches `GetCertificateBundle(serialNumber)` and rebuilds the result with the bundle's chain PEM merged in. A new `-LocalChainOnly` switch opts out of the bundle fetch for strict offline behavior. Bundle-fetch failures are logged at verbose level and the cmdlet returns the local-only result.
- `InfisicalCertificateRequestHelpers.BuildResultFromExistingLocal` adds a second overload that accepts an `InfisicalCertificateBundle`; when supplied, chain certs from the bundle are deduplicated by thumbprint and merged with the locally-resolved chain before classification.
## 2026.06.04.1652
- Build produced from commit 19615363e356.
## Unreleased (carried forward)
## 2026.06.04.1651
- Build produced from commit 19615363e356.
## Unreleased (carried forward)
## 2026.06.04.1634
- Build produced from commit 19615363e356.
## Unreleased (carried forward)
## 2026.06.04.1631
- Build produced from commit 19615363e356.
## Unreleased (carried forward)
## 2026.06.04.1622
- Build produced from commit 19615363e356.
## Unreleased (carried forward)
- **PKI contract fixes and cmdlet expansion**:
- `InfisicalPkiClient` no longer auto-injects `connection.ProjectId` into PKI CA list/retrieve calls; only the caller's explicit `-ProjectId` is forwarded so that cert-manager primary routes (which do not accept the query parameter) succeed.
- List/single CA and single certificate response parsing now tolerate raw arrays, wrapper objects (`{certificate: {...}}`, `{certificates: [...]}`), and nested `configuration` blocks. `InfisicalCaMapper` reads CA detail fields from `configuration` first, falling back to top-level.
- `RetrieveCertificate(connection, identifier)` added on `InfisicalPkiClient`.
- **New cmdlets**:
- **`Get-InfisicalCertificate`** — single-record retrieval by `-SerialNumber`/`-Id` (mandatory positional).
- **`Get-InfisicalCertificates`** — listing with light filtering (`-CommonName`, `-FriendlyName`, `-Status`, `-CaId`, `-Limit`, `-Offset`, `-NoAutoPage`). Auto-paginates by default.
- **`Request-InfisicalCertificate`** — generates a keypair locally (private key never leaves the device), submits a PKCS#10 CSR to either `pki-subscribers/{name}/sign-certificate` (`-PkiSubscriberSlug`) or `ca/{caId}/sign-certificate` (`-CertificateAuthorityId`), and returns a single `InfisicalCertificateResult` object with the leaf and chain pre-classified. The result exposes `Leaf : X509Certificate2`, `Intermediates : X509Certificate2[]`, `Root : X509Certificate2` (nullable), `Chain : X509Certificate2[]` (ordered leaf → intermediates → root, deduplicated by thumbprint), plus pass-through `SerialNumber`, `CertificatePem`, `CertificateChainPem`, and `PrivateKeyPem`. Supports `-Subject` (`IDictionary` with `CN`/`C`/`ST`/`L`/`O`/`OU`/`E` keys) merged with individual `-CommonName`/`-Country`/etc. parameters (individual params win), `-DnsName`/`-IpAddress` SANs (auto-populated from local FQDN when omitted). Idempotency: scans the local `X509Store` for an existing certificate matching `CN` and an Infisical-known serial number; returns the existing certificate wrapped in an `InfisicalCertificateResult` whose `Intermediates`/`Root`/`Chain` are populated by walking the local trust stores via `X509Chain` (no network calls, revocation checks disabled), and whose `CertificatePem`/`CertificateChainPem` are reconstructed from the resolved certs. Reuse is short-circuited unless `-Force` or `-AllowRenewal` (with optional `-RenewalThresholdDays`, default 30) requests a new one. Installation: `-Install` adds the leaf to `-StoreName`/`-StoreLocation` (default `My`/`CurrentUser`); `-InstallChain` additionally places intermediates into `CertificateAuthority` and self-signed roots into `Root` for the same `-StoreLocation`. `-KeyStorageFlags` is passed through to `X509Certificate2` import.
- **Multi-algorithm CSR support** on `Request-InfisicalCertificate` via split parameters: `-KeyAlgorithm` (`Rsa`/`Ecdsa`/`Ed25519`, default `Rsa`), `-KeySize` (`2048`/`3072`/`4096`, default `2048`, applies to RSA only), `-Curve` (`P256`/`P384`, default `P256`, applies to ECDSA only). Signature algorithms are picked automatically: SHA256WITHRSA for RSA, SHA256WITHECDSA / SHA384WITHECDSA for ECDSA P-256/P-384, and Ed25519 (pure-EdDSA) for Ed25519. The underlying `InfisicalCsrBuilder.Build(subject, dns, ip, options)` API was updated to take an `InfisicalCsrOptions` object in place of the prior `keySize` int.
- **Sign-certificate endpoint registrations**: `SignCertificateBySubscriber` and `SignCertificateByCa` registered with both `/api/v1/pki/...` and `/api/v1/cert-manager/...` candidate paths and marked `ContainsSecretMaterialInResponse = true`.
## 2026.06.04.1554
- Build produced from commit 19615363e356.
## Unreleased (carried forward)
## 2026.06.04.1512
- Build produced from commit 19615363e356.
## Unreleased (carried forward)
## 2026.06.04.1508
- Build produced from commit 19615363e356.
## Unreleased (carried forward)
- **CI — Gitea artifact upload fix**: Replaced `actions/upload-artifact@v4` and `actions/download-artifact@v4` with the Gitea-compatible forks `christopherhx/gitea-upload-artifact@v4` and `christopherhx/gitea-download-artifact@v4` in `.gitea/workflows/publish-psgallery.yml`. The upstream v4 actions abort on Gitea because Gitea is detected as GHES, which the upstream v4 actions do not support (see [go-gitea/gitea#28853](https://github.com/go-gitea/gitea/issues/28853)).
## 2026.06.04.0123
@@ -14,7 +205,7 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) loos
## Unreleased (carried forward)
- **M10 polish — formatting, type metadata, and PKI route aliases**:
- **M10 polish — formatting, type metadata, and PKI route aliases**:
- Added default table views and `DefaultDisplayPropertySet` entries for `InfisicalCertificateAuthority`, `InfisicalCertificate`, and `InfisicalCertificateBundle` in the module `Format.ps1xml` / `Types.ps1xml`.
- Realigned PKI endpoint registry to current Infisical paths: `ListInternalCertificateAuthorities` and `RetrieveInternalCertificateAuthority` now use `/api/v1/cert-manager/ca/internal[/{caId}]` as primary, with legacy `/api/v1/pki/ca/internal[/{caId}]` retained as a fallback alias. `GetCertificateBundle` and `RetrieveCertificate` similarly carry `cert-manager` fallback aliases.
- `InfisicalApiInvoker.InvokeWithCandidateFallback` walks the candidate list and falls back on `404`/`405`, used by `InfisicalPkiClient` so older self-hosted Infisical instances are tolerated transparently.
@@ -25,7 +216,7 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) loos
## Unreleased (carried forward)
- **M10 — PKI Internal CAs, Certificates & Windows Store integration**:
- **M10 — PKI Internal CAs, Certificates & Windows Store integration**:
- **`Get-InfisicalCertificateAuthority`** lists internal certificate authorities for the current project, or returns a single CA with `-CaId`.
- **`Search-InfisicalCertificate`** wraps `POST /api/v1/projects/{projectId}/certificates/search` with rich filters (`-CommonName`, `-FriendlyName`, `-Search`, `-Status`, `-CaId`, `-ProfileId`, `-ApplicationId`, `-EnrollmentType`, `-KeyAlgorithm`, `-SignatureAlgorithm`, `-Source`, `-NotAfterFrom/To`, `-NotBeforeFrom/To`, `-SortBy/-SortOrder`, `-Limit/-Offset`). Auto-paginates unless `-NoAutoPage` is set.
- **`ConvertTo-InfisicalCertificate`** accepts an `InfisicalCertificate`, `InfisicalCertificateBundle`, or `-SerialNumber`, fetches the bundle endpoint when needed, and emits a `System.Security.Cryptography.X509Certificates.X509Certificate2` with the private key attached. `-NoPrivateKey` skips key parsing; `-IncludeChain` additionally emits intermediates; `-KeyStorageFlags` controls import behavior.
@@ -52,7 +243,7 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) loos
## 2026.06.03.2207
- Build produced from commit 09c3d5c68bbc.
- **M9 — Bulk, Duplicate & Inheritance**:
- **M9 — Bulk, Duplicate & Inheritance**:
- **Bulk parameter sets** added to `New-InfisicalSecret`, `Update-InfisicalSecret`, and `Remove-InfisicalSecret` accepting `-Secrets Hashtable[]`; client methods `CreateBatch`/`UpdateBatch`/`DeleteBatch` wrap `POST|PATCH|DELETE /api/v3/secrets/batch/raw`.
- **`Copy-InfisicalSecret`** cmdlet added, wrapping `POST /api/v4/secrets/duplicate` with source/destination environment + path parameters and per-attribute copy toggles.
- **Connection inheritance** centralized in `InfisicalCmdletBase` (`ResolveProjectId`/`ResolveEnvironment`/`ResolveSecretPath`/`ResolveApiVersion`/`ResolveOrganizationId`). Explicit parameters always win; missing values fall back to the active connection and emit a `-Verbose` line.
+14 -8
View File
@@ -1,6 +1,6 @@
@{
RootModule = 'PSInfisicalAPI.psm1'
ModuleVersion = '2026.06.04.0123'
ModuleVersion = '2026.06.05.0117'
GUID = 'b8a2f3d4-7c51-4d2f-9e6a-1f0c8b3d4e51'
Author = 'Grace Solutions'
CompanyName = 'Grace Solutions'
@@ -12,7 +12,6 @@
CmdletsToExport = @(
'Connect-Infisical',
'Disconnect-Infisical',
'Get-InfisicalSecrets',
'Get-InfisicalSecret',
'New-InfisicalSecret',
'Update-InfisicalSecret',
@@ -20,32 +19,39 @@
'Copy-InfisicalSecret',
'ConvertTo-InfisicalSecretDictionary',
'Export-InfisicalSecrets',
'Get-InfisicalProjects',
'Get-InfisicalProject',
'New-InfisicalProject',
'Update-InfisicalProject',
'Remove-InfisicalProject',
'Get-InfisicalEnvironments',
'Get-InfisicalEnvironment',
'New-InfisicalEnvironment',
'Update-InfisicalEnvironment',
'Remove-InfisicalEnvironment',
'Get-InfisicalFolders',
'Get-InfisicalFolder',
'New-InfisicalFolder',
'Update-InfisicalFolder',
'Remove-InfisicalFolder',
'Get-InfisicalTags',
'Get-InfisicalTag',
'New-InfisicalTag',
'Update-InfisicalTag',
'Remove-InfisicalTag',
'Get-InfisicalCertificateAuthority',
'Get-InfisicalPkiSubscriber',
'Get-InfisicalCertificateProfile',
'Get-InfisicalCertificatePolicy',
'Get-InfisicalCertificate',
'Search-InfisicalCertificate',
'Request-InfisicalCertificate',
'ConvertTo-InfisicalCertificate',
'Install-InfisicalCertificate',
'Uninstall-InfisicalCertificate',
'Export-InfisicalCertificate'
'Export-InfisicalCertificate',
'Get-InfisicalCertificateApplication',
'Get-InfisicalCertificateApplicationEnrollment',
'New-InfisicalScepDynamicChallenge',
'Get-InfisicalScepMdmProfile',
'Export-InfisicalScepMdmProfile',
'Write-InfisicalScepMdmProfileToWmi'
)
AliasesToExport = @()
VariablesToExport = @()
@@ -57,7 +63,7 @@
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 = '2cbd5c2008f5'
CommitHash = 'cffda99591c9'
}
}
}
Binary file not shown.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+133 -9
View File
@@ -26,14 +26,79 @@ Import-Module -Name .\Module\PSInfisicalAPI
## Cmdlets
The module exports 37 cmdlets. Discovery cmdlets (`Get-Infisical*`) use a `List` (default) / single-record parameter-set pair: invoking without the identity parameter returns the collection, supplying the identity parameter returns one record.
### Session
| 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. |
| ---------------------- | -------------------------------------------------------------------------------------------------- |
| `Connect-Infisical` | Establishes an authenticated session with an Infisical server and stores it for use by subsequent cmdlets. |
| `Disconnect-Infisical` | Clears the current Infisical session from the module-level session manager. |
### Secrets
| Cmdlet | Purpose |
| ------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `Get-InfisicalSecret` | Lists or retrieves Infisical secrets within a project, environment, and optional folder path. |
| `New-InfisicalSecret` | Creates a new Infisical secret, with support for SecureString values and bulk creation. |
| `Update-InfisicalSecret` | Updates an existing Infisical secret value, comment, name, or tags. |
| `Remove-InfisicalSecret` | Deletes one or many Infisical secrets by name. |
| `Copy-InfisicalSecret` | Duplicates one or more secrets into a different environment or secret path. |
| `ConvertTo-InfisicalSecretDictionary` | Converts a stream of InfisicalSecret objects into a name-keyed Dictionary of SecureString or plain text values. |
| `Export-InfisicalSecrets` | Exports InfisicalSecret objects to disk or environment variables in a chosen file format. |
### Projects
| Cmdlet | Purpose |
| ------------------------- | -------------------------------------------------------------------------------------------------- |
| `Get-InfisicalProject` | Lists or retrieves Infisical projects accessible to the current identity. |
| `New-InfisicalProject` | Creates a new Infisical project in the active organization. |
| `Update-InfisicalProject` | Updates the name, description, or auto-capitalization flag on an existing project. |
| `Remove-InfisicalProject` | Deletes an Infisical project. |
### Environments
| Cmdlet | Purpose |
| ----------------------------- | -------------------------------------------------------------------------------------------------- |
| `Get-InfisicalEnvironment` | Lists or retrieves Infisical environments defined on a project. |
| `New-InfisicalEnvironment` | Creates a new environment on an Infisical project. |
| `Update-InfisicalEnvironment` | Updates the name, slug, or sort order of an existing Infisical environment. |
| `Remove-InfisicalEnvironment` | Deletes an Infisical environment from a project. |
### Folders
| Cmdlet | Purpose |
| ------------------------ | -------------------------------------------------------------------------------------------------- |
| `Get-InfisicalFolder` | Lists or retrieves Infisical folders at a given secret path. |
| `New-InfisicalFolder` | Creates a new Infisical folder under the supplied parent path. |
| `Update-InfisicalFolder` | Renames an existing Infisical folder. |
| `Remove-InfisicalFolder` | Deletes an Infisical folder and all secrets it contains. |
### Tags
| Cmdlet | Purpose |
| --------------------- | -------------------------------------------------------------------------------------------------- |
| `Get-InfisicalTag` | Lists or retrieves Infisical tags defined on a project. |
| `New-InfisicalTag` | Creates a new Infisical tag on a project. |
| `Update-InfisicalTag` | Updates the slug, name, or color of an existing Infisical tag. |
| `Remove-InfisicalTag` | Deletes an Infisical tag from a project. |
### PKI
| Cmdlet | Purpose |
| ----------------------------------- | -------------------------------------------------------------------------------------------------- |
| `Get-InfisicalCertificateAuthority` | Lists or retrieves Infisical internal Certificate Authorities. |
| `Get-InfisicalPkiSubscriber` | Lists or retrieves Infisical PKI subscribers in a project. |
| `Get-InfisicalCertificate` | Lists or retrieves Infisical certificates in a project, with optional filters and automatic paging. |
| `Search-InfisicalCertificate` | Searches Infisical certificates with advanced filters and automatic paging. |
| `Request-InfisicalCertificate` | Requests a new Infisical certificate (local CSR + sign) or reuses a still-valid existing one. |
| `ConvertTo-InfisicalCertificate` | Materializes an X509Certificate2 from an Infisical certificate record, bundle, or serial number. |
| `Install-InfisicalCertificate` | Installs an Infisical certificate (and optional chain) into a Windows certificate store. |
| `Uninstall-InfisicalCertificate` | Removes a certificate from a Windows certificate store by thumbprint, subject, or pipeline input. |
| `Export-InfisicalCertificate` | Exports an Infisical certificate to disk in PEM, PFX, or CER format. |
| `Get-InfisicalScepMdmProfile` | Projects an Infisical certificate profile into a Windows SCEP MDM profile model. |
| `Export-InfisicalScepMdmProfile` | Writes a SCEP MDM profile to disk as a SyncML payload suitable for MDM delivery. |
| `Write-InfisicalScepMdmProfileToWmi`| Submits a SCEP MDM profile to the local MDM Bridge WMI provider to trigger enrollment. |
Use `Get-Help <Cmdlet> -Full` for parameter details and `Get-Help about_PSInfisicalAPI` for the module overview.
@@ -51,7 +116,7 @@ $connection = Connect-Infisical `
-ClientSecret $secureSecret `
-PassThru
Get-InfisicalSecrets -SecretPath '/'
Get-InfisicalSecret -SecretPath '/'
Disconnect-Infisical
```
@@ -96,7 +161,7 @@ Sensitive values (`ClientSecret`, `AccessToken`) are read directly into a read-o
[Environment]::SetEnvironmentVariable('INFISICAL_CLIENT_SECRET', 'super-secret-value', 'User')
Connect-Infisical
Get-InfisicalSecrets
Get-InfisicalSecret
```
### Mixed example (explicit values override discovery)
@@ -119,6 +184,65 @@ 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.
## Extending the module
### Adding a new API endpoint
All HTTP routes live in two files under `src/PSInfisicalAPI/Endpoints/`:
- `InfisicalEndpointNames.cs` declares a `const string` identifier for each endpoint.
- `InfisicalEndpointRegistry.cs` maps each identifier to one or more `InfisicalEndpointDefinition` records grouped by resource (`RegisterAuthentication`, `RegisterSecrets`, `RegisterPki`, etc.).
To add a route:
1. Add a constant in `InfisicalEndpointNames.cs` (e.g., `public const string ListPkiSubscribers = "ListPkiSubscribers";`).
2. In the matching `Register<Resource>` method, call `Add(map, new InfisicalEndpointDefinition { ... })` with `Name`, `Resource`, `Version`, `Method`, `Template`, and the `RequiresAuthorization` / `ContainsSecretMaterialInRequest` / `ContainsSecretMaterialInResponse` flags. Use `{placeholder}` tokens in `Template`; they are substituted from the `pathParameters` dictionary passed by the caller.
3. If the same logical operation has more than one upstream path (legacy + current), register both definitions under the same `Name``InvokeWithCandidateFallback` tries each in order until one succeeds.
4. Invoke the endpoint from the appropriate client (`InfisicalPkiClient`, `InfisicalSecretsClient`, etc.) via `_invoker.InvokeWithCandidateFallback(connection, InfisicalEndpointNames.XYZ, "XYZ", pathParameters, query, body)`.
### Adding a new cmdlet
Cmdlets live in `src/PSInfisicalAPI/Cmdlets/` and derive from `InfisicalCmdletBase`, which exposes `HttpClient`, `Logger`, `ResolveProjectId`, and `ThrowTerminatingForException`. Follow the consolidated discovery pattern when the cmdlet supports both list and single-record retrieval:
```csharp
[Cmdlet(VerbsCommon.Get, "InfisicalPkiSubscriber", DefaultParameterSetName = "List")]
[OutputType(typeof(InfisicalPkiSubscriber))]
public sealed class GetInfisicalPkiSubscriberCmdlet : InfisicalCmdletBase
{
[Parameter(ParameterSetName = "ByName", Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)]
[Alias("SubscriberName", "Slug")]
public string Name { get; set; }
[Parameter] public string ProjectId { get; set; }
protected override void ProcessRecord() { /* dispatch on ParameterSetName */ }
}
```
After adding (or removing) a cmdlet:
1. Update `build.ps1` in **two** places — the `CmdletsToExport` array inside the generated manifest block, and the `$expectedCmds` array used by `Test-ModuleImports`. Both must list the same cmdlets; the build fails fast if they drift.
2. Add a `<command:command>` entry in `Module/PSInfisicalAPI/en-US/PSInfisicalAPI.dll-Help.xml`. Each entry must include a non-empty `<maml:description>` synopsis (do not let it start with the cmdlet name — the validation gate rejects PowerShell's auto-generated fallback), a non-empty `<maml:description>` body, and at least one `<command:example>` with a non-empty `<dev:code>` block.
3. For consolidated `List` / single-record cmdlets, ship **three examples**: two straight-line invocations (one per parameter set) and one `OrderedDictionary` splat. The splat must construct the dictionary with `OrdinalIgnoreCase` so parameter names round-trip case-insensitively:
```powershell
$Params = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' -ArgumentList ([System.StringComparer]::OrdinalIgnoreCase)
$Params.ProjectId = (Get-InfisicalProject | Select-Object -First 1).Id
$Result = Get-InfisicalPkiSubscriber @Params
```
4. Add a `## Unreleased` entry to `CHANGELOG.md` describing the change (mark removals of public cmdlets or parameters as **BREAKING**).
5. Run `./build.ps1 -RunTests`. The script enforces the cmdlet list, runs the xUnit suite, and verifies that every exported cmdlet has a valid synopsis, description, and at least one non-empty example.
### Committing source and build artifacts in lockstep
The embedded `BuildCommitHash` in `Module/PSInfisicalAPI/PSInfisicalAPI.psd1` and the bundled DLL is captured from `git rev-parse HEAD` at build time. To keep the embedded hash truthful, commit source and build artifacts as two ordered commits:
1. Stage and commit your source changes first. Suppose this produces commit `S`.
2. Run `./build.ps1 -RunTests -CommitArtifacts`. The build picks up `S` as `HEAD`, embeds it as `BuildCommitHash`, then stages and commits **only** the build outputs (`Module/PSInfisicalAPI/bin/**`, `Module/PSInfisicalAPI/PSInfisicalAPI.psd1`, and the `CHANGELOG.md` build-stamp insertion). The commit message references `S` so the binary commit always traces back to its source.
3. `git push`.
`-CommitArtifacts` only touches the three artifact paths above; any other dirty files in your working tree are left alone. Use the older `-CommitOnSuccess` switch only when you intentionally want a single commit covering everything (`git add -A` + `git commit -m "Build <version>"`); the two switches are mutually exclusive.
## 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.
+114 -12
View File
@@ -15,9 +15,15 @@ param(
[switch]$CommitOnSuccess,
[switch]$CommitArtifacts,
[switch]$Force
)
if ($CommitOnSuccess.IsPresent -and $CommitArtifacts.IsPresent) {
throw "-CommitOnSuccess and -CommitArtifacts are mutually exclusive."
}
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest
@@ -100,7 +106,6 @@ function Write-Manifest {
CmdletsToExport = @(
'Connect-Infisical',
'Disconnect-Infisical',
'Get-InfisicalSecrets',
'Get-InfisicalSecret',
'New-InfisicalSecret',
'Update-InfisicalSecret',
@@ -108,32 +113,39 @@ function Write-Manifest {
'Copy-InfisicalSecret',
'ConvertTo-InfisicalSecretDictionary',
'Export-InfisicalSecrets',
'Get-InfisicalProjects',
'Get-InfisicalProject',
'New-InfisicalProject',
'Update-InfisicalProject',
'Remove-InfisicalProject',
'Get-InfisicalEnvironments',
'Get-InfisicalEnvironment',
'New-InfisicalEnvironment',
'Update-InfisicalEnvironment',
'Remove-InfisicalEnvironment',
'Get-InfisicalFolders',
'Get-InfisicalFolder',
'New-InfisicalFolder',
'Update-InfisicalFolder',
'Remove-InfisicalFolder',
'Get-InfisicalTags',
'Get-InfisicalTag',
'New-InfisicalTag',
'Update-InfisicalTag',
'Remove-InfisicalTag',
'Get-InfisicalCertificateAuthority',
'Get-InfisicalPkiSubscriber',
'Get-InfisicalCertificateProfile',
'Get-InfisicalCertificatePolicy',
'Get-InfisicalCertificate',
'Search-InfisicalCertificate',
'Request-InfisicalCertificate',
'ConvertTo-InfisicalCertificate',
'Install-InfisicalCertificate',
'Uninstall-InfisicalCertificate',
'Export-InfisicalCertificate'
'Export-InfisicalCertificate',
'Get-InfisicalCertificateApplication',
'Get-InfisicalCertificateApplicationEnrollment',
'New-InfisicalScepDynamicChallenge',
'Get-InfisicalScepMdmProfile',
'Export-InfisicalScepMdmProfile',
'Write-InfisicalScepMdmProfileToWmi'
)
AliasesToExport = @()
VariablesToExport = @()
@@ -193,15 +205,50 @@ if (`$null -eq `$manifest) {
Import-Module -Name '$($ModuleDirectory.FullName)' -Force
`$cmds = @('Connect-Infisical','Disconnect-Infisical','Get-InfisicalSecrets','Get-InfisicalSecret','New-InfisicalSecret','Update-InfisicalSecret','Remove-InfisicalSecret','ConvertTo-InfisicalSecretDictionary','Export-InfisicalSecrets','Get-InfisicalProjects','Get-InfisicalProject','New-InfisicalProject','Update-InfisicalProject','Remove-InfisicalProject','Get-InfisicalEnvironments','Get-InfisicalEnvironment','New-InfisicalEnvironment','Update-InfisicalEnvironment','Remove-InfisicalEnvironment','Get-InfisicalFolders','Get-InfisicalFolder','New-InfisicalFolder','Update-InfisicalFolder','Remove-InfisicalFolder','Get-InfisicalTags','Get-InfisicalTag','New-InfisicalTag','Update-InfisicalTag','Remove-InfisicalTag','Get-InfisicalCertificateAuthority','Search-InfisicalCertificate','ConvertTo-InfisicalCertificate','Install-InfisicalCertificate','Uninstall-InfisicalCertificate','Export-InfisicalCertificate')
foreach (`$c in `$cmds) {
if (-not (Get-Command -Name `$c -Module PSInfisicalAPI -ErrorAction SilentlyContinue)) {
throw "Cmdlet not found: `$c"
`$cmds = @(Get-Command -Module PSInfisicalAPI -CommandType Cmdlet)
if (`$cmds.Count -eq 0) {
throw "No cmdlets were exported by the PSInfisicalAPI module."
}
`$help = Get-Help -Name `$c -ErrorAction SilentlyContinue
`$expectedCmds = @('Connect-Infisical','Disconnect-Infisical','Get-InfisicalSecret','New-InfisicalSecret','Update-InfisicalSecret','Remove-InfisicalSecret','Copy-InfisicalSecret','ConvertTo-InfisicalSecretDictionary','Export-InfisicalSecrets','Get-InfisicalProject','New-InfisicalProject','Update-InfisicalProject','Remove-InfisicalProject','Get-InfisicalEnvironment','New-InfisicalEnvironment','Update-InfisicalEnvironment','Remove-InfisicalEnvironment','Get-InfisicalFolder','New-InfisicalFolder','Update-InfisicalFolder','Remove-InfisicalFolder','Get-InfisicalTag','New-InfisicalTag','Update-InfisicalTag','Remove-InfisicalTag','Get-InfisicalCertificateAuthority','Get-InfisicalPkiSubscriber','Get-InfisicalCertificateProfile','Get-InfisicalCertificatePolicy','Get-InfisicalCertificate','Search-InfisicalCertificate','Request-InfisicalCertificate','ConvertTo-InfisicalCertificate','Install-InfisicalCertificate','Uninstall-InfisicalCertificate','Export-InfisicalCertificate','Get-InfisicalCertificateApplication','Get-InfisicalCertificateApplicationEnrollment','New-InfisicalScepDynamicChallenge','Get-InfisicalScepMdmProfile','Export-InfisicalScepMdmProfile','Write-InfisicalScepMdmProfileToWmi')
foreach (`$expected in `$expectedCmds) {
if (-not (Get-Command -Name `$expected -Module PSInfisicalAPI -ErrorAction SilentlyContinue)) {
throw "Cmdlet not found: `$expected"
}
}
foreach (`$cmd in `$cmds) {
`$name = `$cmd.Name
`$help = Get-Help -Name `$name -Full -ErrorAction SilentlyContinue
if (`$null -eq `$help) {
throw "Get-Help returned nothing for cmdlet: `$c"
throw "Get-Help returned nothing for cmdlet: `$name"
}
`$synopsis = (`$help.Synopsis | Out-String).Trim()
if ([string]::IsNullOrWhiteSpace(`$synopsis) -or `$synopsis.StartsWith(`$name, [System.StringComparison]::OrdinalIgnoreCase)) {
throw "Get-Help synopsis is missing or auto-generated for cmdlet: `$name"
}
`$description = (`$help.description | Out-String).Trim()
if ([string]::IsNullOrWhiteSpace(`$description)) {
throw "Get-Help description is empty for cmdlet: `$name"
}
`$examples = Get-Help -Name `$name -Examples -ErrorAction SilentlyContinue
if (`$null -eq `$examples -or `$null -eq `$examples.examples -or `$null -eq `$examples.examples.example) {
throw "Get-Help -Examples returned no examples for cmdlet: `$name"
}
`$exampleNodes = @(`$examples.examples.example)
if (`$exampleNodes.Count -lt 1) {
throw "Get-Help -Examples returned zero examples for cmdlet: `$name"
}
foreach (`$example in `$exampleNodes) {
`$code = (`$example.code | Out-String).Trim()
if ([string]::IsNullOrWhiteSpace(`$code)) {
throw "Example with empty code block found for cmdlet: `$name"
}
}
}
@@ -297,6 +344,35 @@ foreach ($assembly in $desiredAssemblies) {
}
}
Write-Step "Staging cmdlet help XML next to module binary"
$moduleCultureDirs = Get-ChildItem -LiteralPath $ModuleRoot.FullName -Directory -Force -ErrorAction SilentlyContinue |
Where-Object { $_.Name -match '^[a-z]{2}(-[A-Za-z0-9]+)*$' }
foreach ($cultureDir in $moduleCultureDirs) {
$helpXmlSource = [System.IO.FileInfo][System.IO.Path]::Combine($cultureDir.FullName, 'PSInfisicalAPI.dll-Help.xml')
if (-not $helpXmlSource.Exists) { continue }
$binCultureDir = [System.IO.DirectoryInfo][System.IO.Path]::Combine($ModuleBinDir.FullName, $cultureDir.Name)
Ensure-Directory -Directory $binCultureDir
Copy-Item -LiteralPath $helpXmlSource.FullName -Destination $binCultureDir.FullName -Force
}
$primaryHelpXml = [System.IO.FileInfo][System.IO.Path]::Combine($ModuleBinDir.FullName, 'en-US', 'PSInfisicalAPI.dll-Help.xml')
if (-not $primaryHelpXml.Exists) {
throw "Help XML not found at '$($primaryHelpXml.FullName)'. Ensure Module/PSInfisicalAPI/en-US/PSInfisicalAPI.dll-Help.xml exists."
}
try {
[xml]$helpDocument = Get-Content -LiteralPath $primaryHelpXml.FullName -Raw
} catch {
throw "Help XML at '$($primaryHelpXml.FullName)' failed to parse as XML: $_"
}
$helpCommandCount = @($helpDocument.helpItems.command).Count
if ($helpCommandCount -lt 1) {
throw "Help XML at '$($primaryHelpXml.FullName)' contains no <command:command> entries."
}
Write-Step "Help XML contains $helpCommandCount cmdlet entries."
$manifestPath = [System.IO.FileInfo][System.IO.Path]::Combine($ModuleRoot.FullName, 'PSInfisicalAPI.psd1')
Write-Manifest -Path $manifestPath -ModuleVersion $buildVersion -CommitHash $commitHash
@@ -328,4 +404,30 @@ if ($CommitOnSuccess.IsPresent) {
if ($LASTEXITCODE -ne 0) { throw "git commit failed." }
}
if ($CommitArtifacts.IsPresent) {
Write-Step "Committing build artifacts (embedded BuildCommitHash=$commitHash)"
$artifactPaths = @(
[System.IO.Path]::Combine('Module', 'PSInfisicalAPI', 'bin'),
[System.IO.Path]::Combine('Module', 'PSInfisicalAPI', 'PSInfisicalAPI.psd1'),
'CHANGELOG.md'
)
foreach ($artifactPath in $artifactPaths) {
& git -C $RepositoryRoot.FullName add -- $artifactPath
if ($LASTEXITCODE -ne 0) { throw "git add '$artifactPath' failed." }
}
$stagedOutput = & git -C $RepositoryRoot.FullName diff --cached --name-only
if ($LASTEXITCODE -ne 0) { throw "git diff --cached failed." }
$stagedFiles = @($stagedOutput | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
if ($stagedFiles.Count -eq 0) {
Write-Step "No build artifact changes to commit."
} else {
$subject = "Build artifacts for $commitHash"
$body = "Auto-generated by build.ps1 -CommitArtifacts. Build $buildVersion. Module DLL and manifest embed BuildCommitHash=$commitHash, matching the source commit they were produced from."
& git -C $RepositoryRoot.FullName commit -m $subject -m $body
if ($LASTEXITCODE -ne 0) { throw "git commit failed." }
}
}
Write-Step "Build complete."
@@ -12,6 +12,7 @@ namespace PSInfisicalAPI.Tests
private static readonly Type CertDtoType = ModuleAssembly.GetType("PSInfisicalAPI.Pki.InfisicalCertificateResponseDto", true);
private static readonly Type CaMapperType = ModuleAssembly.GetType("PSInfisicalAPI.Pki.InfisicalCaMapper", true);
private static readonly Type CaDtoType = ModuleAssembly.GetType("PSInfisicalAPI.Pki.InfisicalInternalCaResponseDto", true);
private static readonly Type CaConfigDtoType = ModuleAssembly.GetType("PSInfisicalAPI.Pki.InfisicalInternalCaConfigurationDto", true);
private static readonly Type BundleDtoType = ModuleAssembly.GetType("PSInfisicalAPI.Pki.InfisicalCertificateBundleResponseDto", true);
private static InfisicalCertificate InvokeCertMap(object dto, string fallbackProjectId)
@@ -90,6 +91,43 @@ namespace PSInfisicalAPI.Tests
Assert.Equal("proj-fallback", mapped.ProjectId);
}
[Fact]
public void CaMap_Prefers_Configuration_Fields_Over_TopLevel()
{
object cfg = Activator.CreateInstance(CaConfigDtoType);
CaConfigDtoType.GetProperty("FriendlyName").SetValue(cfg, "C=US, CN=GSPA Intermediate");
CaConfigDtoType.GetProperty("CommonName").SetValue(cfg, "GSPA Intermediate");
CaConfigDtoType.GetProperty("OrganizationName").SetValue(cfg, "GSPA");
CaConfigDtoType.GetProperty("OrganizationUnit").SetValue(cfg, "MECM");
CaConfigDtoType.GetProperty("Country").SetValue(cfg, "US");
CaConfigDtoType.GetProperty("KeyAlgorithm").SetValue(cfg, "RSA_2048");
CaConfigDtoType.GetProperty("DistinguishedName").SetValue(cfg, "CN=GSPA Intermediate");
CaConfigDtoType.GetProperty("SerialNumber").SetValue(cfg, "74a4b62197ad");
CaConfigDtoType.GetProperty("MaxPathLength").SetValue(cfg, 0);
CaConfigDtoType.GetProperty("Type").SetValue(cfg, "intermediate");
object dto = Activator.CreateInstance(CaDtoType);
CaDtoType.GetProperty("Id").SetValue(dto, "ca-9");
CaDtoType.GetProperty("Name").SetValue(dto, "intermediate-ca");
CaDtoType.GetProperty("Type").SetValue(dto, "internal");
CaDtoType.GetProperty("Status").SetValue(dto, "active");
CaDtoType.GetProperty("Configuration").SetValue(dto, cfg);
InfisicalCertificateAuthority mapped = InvokeCaMap(dto, "proj-fallback");
Assert.Equal("ca-9", mapped.Id);
Assert.Equal("intermediate-ca", mapped.Name);
Assert.Equal("internal", mapped.Type);
Assert.Equal("C=US, CN=GSPA Intermediate", mapped.FriendlyName);
Assert.Equal("GSPA Intermediate", mapped.CommonName);
Assert.Equal("GSPA", mapped.OrganizationName);
Assert.Equal("MECM", mapped.OrganizationUnit);
Assert.Equal("US", mapped.Country);
Assert.Equal("RSA_2048", mapped.KeyAlgorithm);
Assert.Equal("CN=GSPA Intermediate", mapped.DistinguishedName);
Assert.Equal("74a4b62197ad", mapped.SerialNumber);
Assert.Equal(0, mapped.MaxPathLength);
}
[Fact]
public void BundleMap_Maps_All_Pem_Fields()
{
@@ -5,7 +5,6 @@ using System.Reflection;
using PSInfisicalAPI.Cmdlets;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Logging;
using PSInfisicalAPI.Models;
using Xunit;
namespace PSInfisicalAPI.Tests
@@ -26,21 +25,6 @@ namespace PSInfisicalAPI.Tests
[Cmdlet(VerbsCommon.Get, "TestCmdlet")]
private sealed class TestCmdlet : InfisicalCmdletBase
{
public string CallResolveProjectId(InfisicalConnection connection, string explicitValue)
{
return ResolveProjectId(connection, explicitValue);
}
public string CallResolveEnvironment(InfisicalConnection connection, string explicitValue)
{
return ResolveEnvironment(connection, explicitValue);
}
public string CallResolveSecretPath(InfisicalConnection connection, string explicitValue)
{
return ResolveSecretPath(connection, explicitValue);
}
public string CallResolveApiVersion(InfisicalConnection connection, string explicitValue)
{
return ResolveApiVersion(connection, explicitValue);
@@ -65,60 +49,11 @@ namespace PSInfisicalAPI.Tests
return new InfisicalConnection
{
BaseUri = new Uri("https://app.example.com"),
ProjectId = "proj-conn",
Environment = "prod-conn",
DefaultSecretPath = "/db",
OrganizationId = "org-conn",
PinnedApiVersion = "v3"
};
}
[Fact]
public void Explicit_Value_Overrides_Connection_And_Does_Not_Log()
{
RecordingLogger logger = new RecordingLogger();
TestCmdlet cmdlet = CreateCmdletWith(logger);
string resolved = cmdlet.CallResolveProjectId(ConnectionWithDefaults(), "explicit-proj");
Assert.Equal("explicit-proj", resolved);
Assert.Empty(logger.VerboseEntries);
}
[Fact]
public void Missing_Value_Inherits_From_Connection_And_Logs()
{
RecordingLogger logger = new RecordingLogger();
TestCmdlet cmdlet = CreateCmdletWith(logger);
string resolved = cmdlet.CallResolveProjectId(ConnectionWithDefaults(), null);
Assert.Equal("proj-conn", resolved);
Assert.Single(logger.VerboseEntries);
Assert.Contains("Inherited ProjectId", logger.VerboseEntries[0]);
Assert.Contains("proj-conn", logger.VerboseEntries[0]);
}
[Fact]
public void ResolveSecretPath_Defaults_To_Root_When_Connection_Has_No_Default()
{
RecordingLogger logger = new RecordingLogger();
TestCmdlet cmdlet = CreateCmdletWith(logger);
InfisicalConnection bareConnection = new InfisicalConnection { BaseUri = new Uri("https://app.example.com") };
string resolved = cmdlet.CallResolveSecretPath(bareConnection, null);
Assert.Equal("/", resolved);
}
[Fact]
public void ResolveSecretPath_Inherits_From_Connection_When_Set()
{
RecordingLogger logger = new RecordingLogger();
TestCmdlet cmdlet = CreateCmdletWith(logger);
string resolved = cmdlet.CallResolveSecretPath(ConnectionWithDefaults(), null);
Assert.Equal("/db", resolved);
Assert.Contains(logger.VerboseEntries, v => v.Contains("SecretPath") && v.Contains("/db"));
}
[Fact]
public void ResolveApiVersion_Prefers_PinnedApiVersion_From_Connection()
{
@@ -130,14 +65,24 @@ namespace PSInfisicalAPI.Tests
}
[Fact]
public void ResolveEnvironment_And_ResolveOrganizationId_Inherit()
public void ResolveOrganizationId_Inherits_From_Connection_And_Logs()
{
RecordingLogger logger = new RecordingLogger();
TestCmdlet cmdlet = CreateCmdletWith(logger);
Assert.Equal("prod-conn", cmdlet.CallResolveEnvironment(ConnectionWithDefaults(), null));
Assert.Equal("org-conn", cmdlet.CallResolveOrganizationId(ConnectionWithDefaults(), null));
Assert.Equal(2, logger.VerboseEntries.Count);
Assert.Single(logger.VerboseEntries);
Assert.Contains("OrganizationId", logger.VerboseEntries[0]);
}
[Fact]
public void ResolveOrganizationId_Explicit_Value_Wins_And_Does_Not_Log()
{
RecordingLogger logger = new RecordingLogger();
TestCmdlet cmdlet = CreateCmdletWith(logger);
Assert.Equal("explicit-org", cmdlet.CallResolveOrganizationId(ConnectionWithDefaults(), "explicit-org"));
Assert.Empty(logger.VerboseEntries);
}
}
}
@@ -0,0 +1,478 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Management.Automation;
using System.Reflection;
using PSInfisicalAPI.Endpoints;
using PSInfisicalAPI.Pki;
using Xunit;
namespace PSInfisicalAPI.Tests
{
public class CsrAndRequestCmdletTests
{
private static readonly Assembly ModuleAssembly = typeof(PSInfisicalAPI.Connections.InfisicalConnection).Assembly;
[Fact]
public void CsrBuilder_Rsa2048_Produces_Pem_Csr_And_PrivateKey_With_Subject_And_Sans()
{
InfisicalCsrSubject subject = new InfisicalCsrSubject
{
CommonName = "test.contoso.local",
Organization = "Contoso",
Country = "US"
};
InfisicalCsrOptions options = new InfisicalCsrOptions { KeyAlgorithm = InfisicalKeyAlgorithm.Rsa, RsaKeySize = 2048 };
InfisicalCsrResult result = InfisicalCsrBuilder.Build(subject, new[] { "test.contoso.local", "alt.contoso.local" }, new[] { "10.0.0.5" }, options);
Assert.NotNull(result);
Assert.Contains("BEGIN CERTIFICATE REQUEST", result.CsrPem);
Assert.Contains("END CERTIFICATE REQUEST", result.CsrPem);
Assert.Contains("BEGIN RSA PRIVATE KEY", result.PrivateKeyPem);
Org.BouncyCastle.Pkcs.Pkcs10CertificationRequest pkcs10 = ReadCsr(result.CsrPem);
Assert.True(pkcs10.Verify());
Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters rsa = Assert.IsAssignableFrom<Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters>(pkcs10.GetPublicKey());
Assert.Equal(2048, rsa.Modulus.BitLength);
}
[Theory]
[InlineData(InfisicalEcCurve.P256, "1.2.840.10045.3.1.7")]
[InlineData(InfisicalEcCurve.P384, "1.3.132.0.34")]
public void CsrBuilder_Ecdsa_Produces_Verifiable_Csr(InfisicalEcCurve curve, string expectedCurveOid)
{
InfisicalCsrSubject subject = new InfisicalCsrSubject { CommonName = "ec.contoso.local" };
InfisicalCsrOptions options = new InfisicalCsrOptions { KeyAlgorithm = InfisicalKeyAlgorithm.Ecdsa, EcCurve = curve };
InfisicalCsrResult result = InfisicalCsrBuilder.Build(subject, new[] { "ec.contoso.local" }, null, options);
Assert.Contains("BEGIN CERTIFICATE REQUEST", result.CsrPem);
Assert.True(result.PrivateKeyPem.Contains("BEGIN EC PRIVATE KEY") || result.PrivateKeyPem.Contains("BEGIN PRIVATE KEY"));
Org.BouncyCastle.Pkcs.Pkcs10CertificationRequest pkcs10 = ReadCsr(result.CsrPem);
Assert.True(pkcs10.Verify());
Org.BouncyCastle.Crypto.Parameters.ECPublicKeyParameters ec = Assert.IsAssignableFrom<Org.BouncyCastle.Crypto.Parameters.ECPublicKeyParameters>(pkcs10.GetPublicKey());
Assert.Equal(expectedCurveOid, ec.PublicKeyParamSet.Id);
}
[Fact]
public void CsrBuilder_Ed25519_Produces_Verifiable_Csr()
{
InfisicalCsrSubject subject = new InfisicalCsrSubject { CommonName = "ed.contoso.local" };
InfisicalCsrOptions options = new InfisicalCsrOptions { KeyAlgorithm = InfisicalKeyAlgorithm.Ed25519 };
InfisicalCsrResult result = InfisicalCsrBuilder.Build(subject, new[] { "ed.contoso.local" }, null, options);
Assert.Contains("BEGIN CERTIFICATE REQUEST", result.CsrPem);
Assert.Contains("BEGIN PRIVATE KEY", result.PrivateKeyPem);
Org.BouncyCastle.Pkcs.Pkcs10CertificationRequest pkcs10 = ReadCsr(result.CsrPem);
Assert.True(pkcs10.Verify());
Assert.IsAssignableFrom<Org.BouncyCastle.Crypto.Parameters.Ed25519PublicKeyParameters>(pkcs10.GetPublicKey());
}
[Fact]
public void CsrBuilder_Rsa_Rejects_Invalid_KeySize()
{
InfisicalCsrSubject subject = new InfisicalCsrSubject { CommonName = "test.local" };
InfisicalCsrOptions options = new InfisicalCsrOptions { KeyAlgorithm = InfisicalKeyAlgorithm.Rsa, RsaKeySize = 1024 };
Assert.Throws<ArgumentException>(() => InfisicalCsrBuilder.Build(subject, null, null, options));
}
[Fact]
public void CsrBuilder_Throws_When_CommonName_Missing()
{
InfisicalCsrSubject subject = new InfisicalCsrSubject { Organization = "Contoso" };
Assert.Throws<ArgumentException>(() => InfisicalCsrBuilder.Build(subject, null, null, new InfisicalCsrOptions()));
}
private static Org.BouncyCastle.Pkcs.Pkcs10CertificationRequest ReadCsr(string pem)
{
using (System.IO.StringReader reader = new System.IO.StringReader(pem))
{
Org.BouncyCastle.OpenSsl.PemReader pemReader = new Org.BouncyCastle.OpenSsl.PemReader(reader);
object obj = pemReader.ReadObject();
return Assert.IsType<Org.BouncyCastle.Pkcs.Pkcs10CertificationRequest>(obj);
}
}
[Fact]
public void MergeSubject_Hashtable_Then_Individual_Params_Override()
{
Type helperType = ModuleAssembly.GetType("PSInfisicalAPI.Pki.InfisicalCertificateRequestHelpers", true);
MethodInfo merge = helperType.GetMethod("MergeSubject", BindingFlags.Public | BindingFlags.Static);
Assert.NotNull(merge);
Hashtable subject = new Hashtable { { "CN", "fallback.local" }, { "O", "FallbackOrg" }, { "C", "DE" } };
object result = merge.Invoke(null, new object[] { subject, "explicit.local", null, null, null, "ExplicitOrg", null, null });
PropertyInfo commonNameProp = result.GetType().GetProperty("CommonName");
PropertyInfo organizationProp = result.GetType().GetProperty("Organization");
PropertyInfo countryProp = result.GetType().GetProperty("Country");
Assert.Equal("explicit.local", commonNameProp.GetValue(result));
Assert.Equal("ExplicitOrg", organizationProp.GetValue(result));
Assert.Equal("DE", countryProp.GetValue(result));
}
[Fact]
public void SignCertificateBySubscriber_Uses_Pki_Subscribers_Template()
{
IReadOnlyList<InfisicalEndpointDefinition> candidates = InfisicalEndpointRegistry.GetCandidates(InfisicalEndpointNames.SignCertificateBySubscriber);
Assert.Single(candidates);
InfisicalEndpointDefinition only = candidates[0];
Assert.Equal("/api/v1/pki/subscribers/{subscriberName}/sign-certificate", only.Template);
Assert.Equal("POST", only.Method);
Assert.True(only.RequiresAuthorization);
Assert.True(only.ContainsSecretMaterialInResponse);
}
[Fact]
public void Candidates_For_SignCertificateByCa_Include_Pki_And_CertManager()
{
IReadOnlyList<InfisicalEndpointDefinition> candidates = InfisicalEndpointRegistry.GetCandidates(InfisicalEndpointNames.SignCertificateByCa);
Assert.Contains(candidates, c => c.Template == "/api/v1/pki/ca/{caId}/sign-certificate");
Assert.Contains(candidates, c => c.Template == "/api/v1/cert-manager/ca/{caId}/sign-certificate");
}
[Fact]
public void RequestInfisicalCertificate_Cmdlet_Has_Both_Parameter_Sets()
{
Type cmdletType = ModuleAssembly.GetType("PSInfisicalAPI.Cmdlets.RequestInfisicalCertificateCmdlet", true);
Assert.True(typeof(PSInfisicalAPI.Cmdlets.InfisicalCmdletBase).IsAssignableFrom(cmdletType));
CustomAttributeData cmdletData = null;
foreach (CustomAttributeData candidate in cmdletType.GetCustomAttributesData())
{
if (candidate.AttributeType == typeof(CmdletAttribute)) { cmdletData = candidate; break; }
}
Assert.NotNull(cmdletData);
Assert.Equal(VerbsLifecycle.Request, cmdletData.ConstructorArguments[0].Value);
Assert.Equal("InfisicalCertificate", cmdletData.ConstructorArguments[1].Value);
string defaultParameterSetName = null;
foreach (CustomAttributeNamedArgument named in cmdletData.NamedArguments)
{
if (named.MemberName == "DefaultParameterSetName") { defaultParameterSetName = (string)named.TypedValue.Value; break; }
}
Assert.Equal("BySubscriber", defaultParameterSetName);
Assert.NotNull(cmdletType.GetProperty("PkiSubscriberSlug"));
Assert.NotNull(cmdletType.GetProperty("CertificateAuthorityId"));
Assert.NotNull(cmdletType.GetProperty("CertificateProfileId"));
Assert.NotNull(cmdletType.GetProperty("Subject"));
Assert.NotNull(cmdletType.GetProperty("CommonName"));
Assert.NotNull(cmdletType.GetProperty("DnsName"));
Assert.NotNull(cmdletType.GetProperty("IpAddress"));
Assert.NotNull(cmdletType.GetProperty("Install"));
Assert.NotNull(cmdletType.GetProperty("StoreName"));
Assert.NotNull(cmdletType.GetProperty("StoreLocation"));
Assert.NotNull(cmdletType.GetProperty("AllowRenewal"));
Assert.NotNull(cmdletType.GetProperty("RenewalThresholdDays"));
Assert.NotNull(cmdletType.GetProperty("Force"));
Assert.NotNull(cmdletType.GetProperty("InstallChain"));
PropertyInfo keyAlgorithmProp = cmdletType.GetProperty("KeyAlgorithm");
PropertyInfo curveProp = cmdletType.GetProperty("Curve");
Assert.NotNull(keyAlgorithmProp);
Assert.NotNull(curveProp);
Assert.Equal(typeof(InfisicalKeyAlgorithm), keyAlgorithmProp.PropertyType);
Assert.Equal(typeof(InfisicalEcCurve), curveProp.PropertyType);
PropertyInfo protectionProp = cmdletType.GetProperty("PrivateKeyProtection");
Assert.NotNull(protectionProp);
Assert.Equal(typeof(InfisicalPrivateKeyProtection), protectionProp.PropertyType);
Assert.NotNull(cmdletType.GetProperty("PersistKey"));
Assert.NotNull(cmdletType.GetProperty("MachineKey"));
Assert.NotNull(cmdletType.GetProperty("PrivateKeyPath"));
Assert.NotNull(cmdletType.GetProperty("LocalChainOnly"));
CustomAttributeData outputTypeData = null;
foreach (CustomAttributeData candidate in cmdletType.GetCustomAttributesData())
{
if (candidate.AttributeType == typeof(OutputTypeAttribute)) { outputTypeData = candidate; break; }
}
Assert.NotNull(outputTypeData);
IList<CustomAttributeTypedArgument> outputTypeArgs = (IList<CustomAttributeTypedArgument>)outputTypeData.ConstructorArguments[0].Value;
Assert.Contains(outputTypeArgs, a => (Type)a.Value == typeof(PSInfisicalAPI.Models.InfisicalCertificateResult));
}
[Fact]
public void BuildResult_Splits_Chain_Into_Leaf_Intermediates_And_Root()
{
(string leafPem, _, string leafThumb) = PemCertificateBuilderTests.CreateSelfSignedExposed("BuildResult.Leaf");
(string intermediatePem, _, string intermediateThumb) = PemCertificateBuilderTests.CreateSelfSignedExposed("BuildResult.Intermediate");
(string rootPem, _, string rootThumb) = PemCertificateBuilderTests.CreateSelfSignedExposed("BuildResult.Root");
PSInfisicalAPI.Models.InfisicalSignedCertificate signed = new PSInfisicalAPI.Models.InfisicalSignedCertificate
{
SerialNumber = "ABC123",
CertificatePem = leafPem,
CertificateChainPem = intermediatePem + rootPem,
IssuingCaCertificatePem = rootPem
};
using (System.Security.Cryptography.X509Certificates.X509Certificate2 leaf = new System.Security.Cryptography.X509Certificates.X509Certificate2(System.Text.Encoding.ASCII.GetBytes(leafPem)))
{
Type helperType = ModuleAssembly.GetType("PSInfisicalAPI.Pki.InfisicalCertificateRequestHelpers", true);
MethodInfo buildResult = helperType.GetMethod("BuildResult", BindingFlags.Public | BindingFlags.Static);
Assert.NotNull(buildResult);
PSInfisicalAPI.Models.InfisicalCertificateResult result = (PSInfisicalAPI.Models.InfisicalCertificateResult)buildResult.Invoke(null, new object[] { leaf, signed });
Assert.Same(leaf, result.Leaf);
Assert.Equal("ABC123", result.SerialNumber);
Assert.Empty(result.Intermediates);
Assert.NotNull(result.Root);
Assert.Equal(2, result.Chain.Length);
Assert.Same(leaf, result.Chain[0]);
}
}
[Theory]
[InlineData(InfisicalPrivateKeyProtection.LocalOnly, false, false, System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.DefaultKeySet)]
[InlineData(InfisicalPrivateKeyProtection.Exportable, false, false, System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.Exportable)]
[InlineData(InfisicalPrivateKeyProtection.NonExportable, false, false, System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.DefaultKeySet)]
[InlineData(InfisicalPrivateKeyProtection.LocalOnly, true, false, System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.PersistKeySet)]
[InlineData(InfisicalPrivateKeyProtection.LocalOnly, false, true, System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.MachineKeySet)]
[InlineData(InfisicalPrivateKeyProtection.Exportable, true, true, System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.Exportable | System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.MachineKeySet | System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.PersistKeySet)]
public void ResolveKeyStorageFlags_Maps_Protection_And_Switches(InfisicalPrivateKeyProtection protection, bool persist, bool machine, System.Security.Cryptography.X509Certificates.X509KeyStorageFlags expected)
{
Type helperType = ModuleAssembly.GetType("PSInfisicalAPI.Pki.InfisicalCertificateRequestHelpers", true);
MethodInfo method = helperType.GetMethod("ResolveKeyStorageFlags", BindingFlags.Public | BindingFlags.Static);
Assert.NotNull(method);
System.Security.Cryptography.X509Certificates.X509KeyStorageFlags actual = (System.Security.Cryptography.X509Certificates.X509KeyStorageFlags)method.Invoke(null, new object[] { protection, persist, machine });
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(InfisicalPrivateKeyProtection.LocalOnly, false, false)]
[InlineData(InfisicalPrivateKeyProtection.Exportable, false, false)]
[InlineData(InfisicalPrivateKeyProtection.NonExportable, false, true)]
[InlineData(InfisicalPrivateKeyProtection.Ephemeral, false, true)]
[InlineData(InfisicalPrivateKeyProtection.LocalOnly, true, true)]
[InlineData(InfisicalPrivateKeyProtection.Exportable, true, true)]
public void ShouldScrubPrivateKeyPem_Returns_Expected(InfisicalPrivateKeyProtection protection, bool hasPath, bool expected)
{
Type helperType = ModuleAssembly.GetType("PSInfisicalAPI.Pki.InfisicalCertificateRequestHelpers", true);
MethodInfo method = helperType.GetMethod("ShouldScrubPrivateKeyPem", BindingFlags.Public | BindingFlags.Static);
Assert.NotNull(method);
bool actual = (bool)method.Invoke(null, new object[] { protection, hasPath });
Assert.Equal(expected, actual);
}
[Fact]
public void WritePrivateKeyPem_Writes_File_And_Creates_Directory()
{
Type helperType = ModuleAssembly.GetType("PSInfisicalAPI.Pki.InfisicalCertificateRequestHelpers", true);
MethodInfo method = helperType.GetMethod("WritePrivateKeyPem", BindingFlags.Public | BindingFlags.Static);
Assert.NotNull(method);
string tempRoot = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "PSInfisicalAPI_PemWrite_" + Guid.NewGuid().ToString("N"));
string nested = System.IO.Path.Combine(tempRoot, "nested", "key.pem");
const string pem = "-----BEGIN PRIVATE KEY-----\nMIIBVgIBADANBgkqhkiG9w0BAQEFAA==\n-----END PRIVATE KEY-----\n";
try
{
method.Invoke(null, new object[] { pem, nested });
Assert.True(System.IO.File.Exists(nested));
Assert.Equal(pem, System.IO.File.ReadAllText(nested));
}
finally
{
if (System.IO.Directory.Exists(tempRoot)) { System.IO.Directory.Delete(tempRoot, true); }
}
}
[Fact]
public void BuildResultFromExistingLocal_Populates_Leaf_And_Pem_For_Selfsigned()
{
(string leafPem, _, string leafThumb) = PemCertificateBuilderTests.CreateSelfSignedExposed("ReuseLookup.Leaf");
using (System.Security.Cryptography.X509Certificates.X509Certificate2 leaf = new System.Security.Cryptography.X509Certificates.X509Certificate2(System.Text.Encoding.ASCII.GetBytes(leafPem)))
{
Type helperType = ModuleAssembly.GetType("PSInfisicalAPI.Pki.InfisicalCertificateRequestHelpers", true);
MethodInfo build = helperType.GetMethod("BuildResultFromExistingLocal", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(System.Security.Cryptography.X509Certificates.X509Certificate2) }, null);
Assert.NotNull(build);
PSInfisicalAPI.Models.InfisicalCertificateResult result = (PSInfisicalAPI.Models.InfisicalCertificateResult)build.Invoke(null, new object[] { leaf });
Assert.Same(leaf, result.Leaf);
Assert.Equal(leaf.SerialNumber, result.SerialNumber);
Assert.Contains("BEGIN CERTIFICATE", result.CertificatePem);
Assert.NotNull(result.Chain);
Assert.NotEmpty(result.Chain);
Assert.Same(leaf, result.Chain[0]);
Assert.Empty(result.Intermediates);
}
}
[Fact]
public void BuildResultFromExistingLocal_Has_Bundle_Fallback_Overload()
{
Type helperType = ModuleAssembly.GetType("PSInfisicalAPI.Pki.InfisicalCertificateRequestHelpers", true);
MethodInfo overload = helperType.GetMethod(
"BuildResultFromExistingLocal",
BindingFlags.Public | BindingFlags.Static,
null,
new Type[] { typeof(System.Security.Cryptography.X509Certificates.X509Certificate2), typeof(PSInfisicalAPI.Models.InfisicalCertificateBundle) },
null);
Assert.NotNull(overload);
Assert.Equal(typeof(PSInfisicalAPI.Models.InfisicalCertificateResult), overload.ReturnType);
}
[Fact]
public void BuildResultFromExistingLocal_With_Null_Bundle_Matches_LocalOnly_Behavior()
{
(string leafPem, _, _) = PemCertificateBuilderTests.CreateSelfSignedExposed("ReuseLookup.Bundle.Null.Leaf");
using (System.Security.Cryptography.X509Certificates.X509Certificate2 leaf = new System.Security.Cryptography.X509Certificates.X509Certificate2(System.Text.Encoding.ASCII.GetBytes(leafPem)))
{
Type helperType = ModuleAssembly.GetType("PSInfisicalAPI.Pki.InfisicalCertificateRequestHelpers", true);
MethodInfo overload = helperType.GetMethod(
"BuildResultFromExistingLocal",
BindingFlags.Public | BindingFlags.Static,
null,
new Type[] { typeof(System.Security.Cryptography.X509Certificates.X509Certificate2), typeof(PSInfisicalAPI.Models.InfisicalCertificateBundle) },
null);
PSInfisicalAPI.Models.InfisicalCertificateResult result = (PSInfisicalAPI.Models.InfisicalCertificateResult)overload.Invoke(null, new object[] { leaf, null });
Assert.Same(leaf, result.Leaf);
Assert.Empty(result.Intermediates);
Assert.Single(result.Chain);
}
}
[Fact]
public void BuildResultFromExistingLocal_With_Bundle_Merges_Chain_From_Bundle()
{
(string leafPem, _, string leafThumb) = PemCertificateBuilderTests.CreateSelfSignedExposed("ReuseLookup.Bundle.Leaf");
(string caPem, _, string caThumb) = PemCertificateBuilderTests.CreateSelfSignedExposed("ReuseLookup.Bundle.Ca");
using (System.Security.Cryptography.X509Certificates.X509Certificate2 leaf = new System.Security.Cryptography.X509Certificates.X509Certificate2(System.Text.Encoding.ASCII.GetBytes(leafPem)))
{
PSInfisicalAPI.Models.InfisicalCertificateBundle bundle = new PSInfisicalAPI.Models.InfisicalCertificateBundle
{
SerialNumber = leaf.SerialNumber,
CertificatePem = leafPem,
CertificateChainPem = caPem
};
Type helperType = ModuleAssembly.GetType("PSInfisicalAPI.Pki.InfisicalCertificateRequestHelpers", true);
MethodInfo overload = helperType.GetMethod(
"BuildResultFromExistingLocal",
BindingFlags.Public | BindingFlags.Static,
null,
new Type[] { typeof(System.Security.Cryptography.X509Certificates.X509Certificate2), typeof(PSInfisicalAPI.Models.InfisicalCertificateBundle) },
null);
PSInfisicalAPI.Models.InfisicalCertificateResult result = (PSInfisicalAPI.Models.InfisicalCertificateResult)overload.Invoke(null, new object[] { leaf, bundle });
Assert.Same(leaf, result.Leaf);
Assert.NotNull(result.Root);
Assert.Equal(caThumb, result.Root.Thumbprint);
Assert.Equal(2, result.Chain.Length);
Assert.Same(leaf, result.Chain[0]);
Assert.Equal(caThumb, result.Chain[1].Thumbprint);
Assert.NotNull(result.CertificateChainPem);
Assert.Contains("BEGIN CERTIFICATE", result.CertificateChainPem);
}
}
[Fact]
public void GetChainCertificateTargetStore_SelfSigned_Returns_Root()
{
using (System.Security.Cryptography.RSA rsa = System.Security.Cryptography.RSA.Create(2048))
{
System.Security.Cryptography.X509Certificates.CertificateRequest request = new System.Security.Cryptography.X509Certificates.CertificateRequest(
"CN=ChainRouting.SelfSigned",
rsa,
System.Security.Cryptography.HashAlgorithmName.SHA256,
System.Security.Cryptography.RSASignaturePadding.Pkcs1);
using (System.Security.Cryptography.X509Certificates.X509Certificate2 selfSigned = request.CreateSelfSigned(DateTimeOffset.UtcNow.AddMinutes(-5), DateTimeOffset.UtcNow.AddDays(1)))
{
Type helperType = ModuleAssembly.GetType("PSInfisicalAPI.Pki.InfisicalCertificateRequestHelpers", true);
MethodInfo classify = helperType.GetMethod("GetChainCertificateTargetStore", BindingFlags.Public | BindingFlags.Static);
Assert.NotNull(classify);
object result = classify.Invoke(null, new object[] { selfSigned });
Assert.Equal(System.Security.Cryptography.X509Certificates.StoreName.Root, result);
}
}
}
[Fact]
public void GetChainCertificateTargetStore_NonSelfSigned_Returns_CertificateAuthority()
{
using (System.Security.Cryptography.RSA rootRsa = System.Security.Cryptography.RSA.Create(2048))
using (System.Security.Cryptography.RSA intermediateRsa = System.Security.Cryptography.RSA.Create(2048))
{
System.Security.Cryptography.X509Certificates.CertificateRequest rootRequest = new System.Security.Cryptography.X509Certificates.CertificateRequest(
"CN=ChainRouting.Root",
rootRsa,
System.Security.Cryptography.HashAlgorithmName.SHA256,
System.Security.Cryptography.RSASignaturePadding.Pkcs1);
rootRequest.CertificateExtensions.Add(new System.Security.Cryptography.X509Certificates.X509BasicConstraintsExtension(true, false, 0, true));
using (System.Security.Cryptography.X509Certificates.X509Certificate2 rootCert = rootRequest.CreateSelfSigned(DateTimeOffset.UtcNow.AddMinutes(-5), DateTimeOffset.UtcNow.AddDays(1)))
{
System.Security.Cryptography.X509Certificates.CertificateRequest intermediateRequest = new System.Security.Cryptography.X509Certificates.CertificateRequest(
"CN=ChainRouting.Intermediate",
intermediateRsa,
System.Security.Cryptography.HashAlgorithmName.SHA256,
System.Security.Cryptography.RSASignaturePadding.Pkcs1);
intermediateRequest.CertificateExtensions.Add(new System.Security.Cryptography.X509Certificates.X509BasicConstraintsExtension(true, false, 0, true));
byte[] serial = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };
using (System.Security.Cryptography.X509Certificates.X509Certificate2 intermediate = intermediateRequest.Create(rootCert, DateTimeOffset.UtcNow.AddMinutes(-5), DateTimeOffset.UtcNow.AddDays(1), serial))
{
Assert.NotEqual(intermediate.Subject, intermediate.Issuer);
Type helperType = ModuleAssembly.GetType("PSInfisicalAPI.Pki.InfisicalCertificateRequestHelpers", true);
MethodInfo classify = helperType.GetMethod("GetChainCertificateTargetStore", BindingFlags.Public | BindingFlags.Static);
object result = classify.Invoke(null, new object[] { intermediate });
Assert.Equal(System.Security.Cryptography.X509Certificates.StoreName.CertificateAuthority, result);
}
}
}
}
[Fact]
public void InstallChain_Has_X509Collection_Overload()
{
Type helperType = ModuleAssembly.GetType("PSInfisicalAPI.Pki.InfisicalCertificateRequestHelpers", true);
Type loggerType = ModuleAssembly.GetType("PSInfisicalAPI.Logging.IInfisicalLogger", true);
MethodInfo overload = helperType.GetMethod(
"InstallChain",
BindingFlags.Public | BindingFlags.Static,
null,
new Type[]
{
typeof(System.Collections.Generic.IEnumerable<System.Security.Cryptography.X509Certificates.X509Certificate2>),
typeof(System.Security.Cryptography.X509Certificates.StoreLocation),
typeof(bool),
loggerType,
typeof(string)
},
null);
Assert.NotNull(overload);
Assert.Equal(typeof(void), overload.ReturnType);
}
[Fact]
public void InstallInfisicalCertificateCmdlet_Uses_ChainRouting_Helper()
{
Type cmdletType = ModuleAssembly.GetType("PSInfisicalAPI.Cmdlets.InstallInfisicalCertificateCmdlet", true);
Assert.NotNull(cmdletType);
Type helperType = ModuleAssembly.GetType("PSInfisicalAPI.Pki.InfisicalCertificateRequestHelpers", true);
MethodInfo classify = helperType.GetMethod("GetChainCertificateTargetStore", BindingFlags.Public | BindingFlags.Static);
Assert.NotNull(classify);
}
}
}
@@ -26,26 +26,6 @@ namespace PSInfisicalAPI.Tests
Assert.True(MatchesAny(name, InfisicalEnvironmentResolver.OrganizationIdPatterns), "Expected match for " + name);
}
[Theory]
[InlineData("INFISICAL_PROJECT_ID")]
[InlineData("INFISICAL_WORKSPACE_ID")]
[InlineData("CLOUDINIT_INFISICAL_PROJECTID")]
public void ProjectIdPatterns_Match_Expected_Names(string name)
{
Assert.True(MatchesAny(name, InfisicalEnvironmentResolver.ProjectIdPatterns), "Expected match for " + name);
}
[Theory]
[InlineData("INFISICAL_ENVIRONMENT")]
[InlineData("INFISICAL_ENVIRONMENT_NAME")]
[InlineData("INFISICAL_ENV")]
[InlineData("INFISICAL_ENV_SLUG")]
[InlineData("CLOUDINIT_INFISICAL_ENVIRONMENT")]
public void EnvironmentPatterns_Match_Expected_Names(string name)
{
Assert.True(MatchesAny(name, InfisicalEnvironmentResolver.EnvironmentPatterns), "Expected match for " + name);
}
[Theory]
[InlineData("INFISICAL_CLIENT_ID")]
[InlineData("INFISICAL_UNIVERSAL_AUTH_CLIENT_ID")]
@@ -78,15 +58,6 @@ namespace PSInfisicalAPI.Tests
Assert.True(MatchesAny(name, InfisicalEnvironmentResolver.AccessTokenPatterns), "Expected match for " + name);
}
[Theory]
[InlineData("INFISICAL_SECRET_PATH")]
[InlineData("INFISICAL_DEFAULT_SECRET_PATH")]
[InlineData("CLOUDINIT_INFISICAL_SECRETPATH")]
public void SecretPathPatterns_Match_Expected_Names(string name)
{
Assert.True(MatchesAny(name, InfisicalEnvironmentResolver.SecretPathPatterns), "Expected match for " + name);
}
[Theory]
[InlineData("INFISICAL_SECRET_PATH")]
[InlineData("INFISICAL_DEFAULT_SECRET_PATH")]
@@ -108,9 +79,6 @@ namespace PSInfisicalAPI.Tests
Assert.False(MatchesAny(name, InfisicalEnvironmentResolver.AccessTokenPatterns));
Assert.False(MatchesAny(name, InfisicalEnvironmentResolver.BaseUriPatterns));
Assert.False(MatchesAny(name, InfisicalEnvironmentResolver.OrganizationIdPatterns));
Assert.False(MatchesAny(name, InfisicalEnvironmentResolver.ProjectIdPatterns));
Assert.False(MatchesAny(name, InfisicalEnvironmentResolver.EnvironmentPatterns));
Assert.False(MatchesAny(name, InfisicalEnvironmentResolver.SecretPathPatterns));
Assert.False(MatchesAny(name, InfisicalEnvironmentResolver.ApiVersionPatterns));
}
@@ -8,6 +8,11 @@ namespace PSInfisicalAPI.Tests
{
public class PemCertificateBuilderTests
{
public static (string CertPem, string KeyPem, string Thumbprint) CreateSelfSignedExposed(string commonName)
{
return CreateSelfSigned(commonName);
}
private static (string CertPem, string KeyPem, string Thumbprint) CreateSelfSigned(string commonName)
{
using (RSA rsa = RSA.Create(2048))
@@ -0,0 +1,86 @@
using System;
using System.Collections;
using System.Reflection;
using PSInfisicalAPI.Http;
using PSInfisicalAPI.Logging;
using PSInfisicalAPI.Pki;
using Xunit;
namespace PSInfisicalAPI.Tests
{
public class PkiClientParseTests
{
private sealed class NoopHttpClient : IInfisicalHttpClient
{
public InfisicalHttpResponse Send(InfisicalHttpRequest request) { throw new NotImplementedException(); }
}
private static InfisicalPkiClient CreateClient()
{
return new InfisicalPkiClient(new NoopHttpClient(), NullInfisicalLogger.Instance);
}
private static object InvokeNonPublic(InfisicalPkiClient client, string methodName, string body)
{
MethodInfo method = typeof(InfisicalPkiClient).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance);
return method.Invoke(client, new object[] { body });
}
[Fact]
public void ParseCaListBody_Reads_Raw_Json_Array()
{
string body = "[{\"id\":\"ca-1\",\"name\":\"intermediate\",\"projectId\":\"p1\",\"configuration\":{\"commonName\":\"Intermediate CA\",\"keyAlgorithm\":\"RSA_2048\"}}]";
object result = InvokeNonPublic(CreateClient(), "ParseCaListBody", body);
IList list = (IList)result;
Assert.Single(list);
object dto = list[0];
Assert.Equal("ca-1", dto.GetType().GetProperty("Id").GetValue(dto));
object cfg = dto.GetType().GetProperty("Configuration").GetValue(dto);
Assert.NotNull(cfg);
Assert.Equal("Intermediate CA", cfg.GetType().GetProperty("CommonName").GetValue(cfg));
}
[Fact]
public void ParseCaListBody_Reads_CertificateAuthorities_Wrapper()
{
string body = "{\"certificateAuthorities\":[{\"id\":\"ca-2\",\"name\":\"root\"}]}";
object result = InvokeNonPublic(CreateClient(), "ParseCaListBody", body);
IList list = (IList)result;
Assert.Single(list);
object dto = list[0];
Assert.Equal("ca-2", dto.GetType().GetProperty("Id").GetValue(dto));
}
[Fact]
public void ParseCaSingleBody_Reads_Raw_Object_With_Configuration()
{
string body = "{\"id\":\"ca-9\",\"name\":\"intermediate-ca\",\"status\":\"active\",\"configuration\":{\"commonName\":\"GSPA Intermediate\",\"organization\":\"GSPA\"}}";
object result = InvokeNonPublic(CreateClient(), "ParseCaSingleBody", body);
Assert.NotNull(result);
Assert.Equal("ca-9", result.GetType().GetProperty("Id").GetValue(result));
object cfg = result.GetType().GetProperty("Configuration").GetValue(result);
Assert.NotNull(cfg);
Assert.Equal("GSPA Intermediate", cfg.GetType().GetProperty("CommonName").GetValue(cfg));
Assert.Equal("GSPA", cfg.GetType().GetProperty("OrganizationName").GetValue(cfg));
}
[Fact]
public void ParseCaSingleBody_Reads_CertificateAuthority_Wrapper()
{
string body = "{\"certificateAuthority\":{\"id\":\"ca-7\",\"name\":\"root\"}}";
object result = InvokeNonPublic(CreateClient(), "ParseCaSingleBody", body);
Assert.NotNull(result);
Assert.Equal("ca-7", result.GetType().GetProperty("Id").GetValue(result));
}
[Fact]
public void ParseCertificateSingleBody_Reads_Certificate_Wrapper()
{
string body = "{\"certificate\":{\"id\":\"cert-1\",\"serialNumber\":\"ABCD\",\"commonName\":\"host.example\"}}";
object result = InvokeNonPublic(CreateClient(), "ParseCertificateSingleBody", body);
Assert.NotNull(result);
Assert.Equal("cert-1", result.GetType().GetProperty("Id").GetValue(result));
Assert.Equal("ABCD", result.GetType().GetProperty("SerialNumber").GetValue(result));
}
}
}
@@ -1,4 +1,7 @@
using System;
using System.Collections.Generic;
using System.Management.Automation;
using System.Reflection;
using PSInfisicalAPI.Endpoints;
using Xunit;
@@ -6,6 +9,65 @@ namespace PSInfisicalAPI.Tests
{
public class PkiEndpointRegistryTests
{
private static readonly Assembly ModuleAssembly = typeof(PSInfisicalAPI.Connections.InfisicalConnection).Assembly;
[Fact]
public void GetInfisicalCertificate_Cmdlet_Is_Singular_With_SerialNumber_In_Single_ParameterSet()
{
Type cmdletType = ModuleAssembly.GetType("PSInfisicalAPI.Cmdlets.GetInfisicalCertificateCmdlet", true);
Assert.True(typeof(PSInfisicalAPI.Cmdlets.InfisicalCmdletBase).IsAssignableFrom(cmdletType));
CustomAttributeData cmdletData = null;
foreach (CustomAttributeData candidate in cmdletType.GetCustomAttributesData())
{
if (candidate.AttributeType == typeof(CmdletAttribute)) { cmdletData = candidate; break; }
}
Assert.NotNull(cmdletData);
Assert.Equal(2, cmdletData.ConstructorArguments.Count);
Assert.Equal(VerbsCommon.Get, cmdletData.ConstructorArguments[0].Value);
Assert.Equal("InfisicalCertificate", cmdletData.ConstructorArguments[1].Value);
string defaultParameterSetName = null;
foreach (CustomAttributeNamedArgument named in cmdletData.NamedArguments)
{
if (named.MemberName == "DefaultParameterSetName") { defaultParameterSetName = (string)named.TypedValue.Value; break; }
}
Assert.Equal("List", defaultParameterSetName);
PropertyInfo serialProp = cmdletType.GetProperty("SerialNumber");
Assert.NotNull(serialProp);
CustomAttributeData parameterAttr = null;
foreach (CustomAttributeData candidate in serialProp.GetCustomAttributesData())
{
if (candidate.AttributeType == typeof(ParameterAttribute)) { parameterAttr = candidate; break; }
}
Assert.NotNull(parameterAttr);
bool mandatory = false;
string parameterSetName = null;
foreach (CustomAttributeNamedArgument named in parameterAttr.NamedArguments)
{
if (named.MemberName == "Mandatory") { mandatory = (bool)named.TypedValue.Value; }
else if (named.MemberName == "ParameterSetName") { parameterSetName = (string)named.TypedValue.Value; }
}
Assert.True(mandatory);
Assert.Equal("Single", parameterSetName);
}
[Fact]
public void GetInfisicalCertificate_Cmdlet_Exposes_List_Filter_Properties()
{
Type cmdletType = ModuleAssembly.GetType("PSInfisicalAPI.Cmdlets.GetInfisicalCertificateCmdlet", true);
Assert.NotNull(cmdletType.GetProperty("CommonName"));
Assert.NotNull(cmdletType.GetProperty("FriendlyName"));
Assert.NotNull(cmdletType.GetProperty("CaId"));
Assert.NotNull(cmdletType.GetProperty("Limit"));
Assert.NotNull(cmdletType.GetProperty("Offset"));
Assert.NotNull(cmdletType.GetProperty("NoAutoPage"));
Assert.NotNull(cmdletType.GetProperty("List"));
}
[Fact]
public void Get_ListInternalCertificateAuthorities_Returns_CertManager_Primary()
{
@@ -33,18 +33,6 @@ namespace PSInfisicalAPI.Authentication
new Regex(@".*INFISICAL.*ORG(ANIZATION)?.*ID.*", DefaultRegexOptions)
};
public static readonly Regex[] ProjectIdPatterns = new[]
{
new Regex(@".*INFISICAL.*(PROJECT|WORKSPACE).*ID.*", DefaultRegexOptions)
};
public static readonly Regex[] EnvironmentPatterns = new[]
{
new Regex(@".*INFISICAL.*ENV(IRONMENT)?.*NAME.*", DefaultRegexOptions),
new Regex(@".*INFISICAL.*ENV(IRONMENT)?.*SLUG.*", DefaultRegexOptions),
new Regex(@".*INFISICAL.*ENV(IRONMENT)?.*", DefaultRegexOptions)
};
public static readonly Regex[] ClientIdPatterns = new[]
{
new Regex(@".*INFISICAL.*CLIENT.*ID.*", DefaultRegexOptions),
@@ -64,12 +52,6 @@ namespace PSInfisicalAPI.Authentication
new Regex(@".*INFISICAL.*TOKEN.*", DefaultRegexOptions)
};
public static readonly Regex[] SecretPathPatterns = new[]
{
new Regex(@".*INFISICAL.*SECRET.*PATH.*", DefaultRegexOptions),
new Regex(@".*INFISICAL.*DEFAULT.*PATH.*", DefaultRegexOptions)
};
public static readonly Regex[] ApiVersionPatterns = new[]
{
new Regex(@".*INFISICAL.*API.*VERSION.*", DefaultRegexOptions)
@@ -28,12 +28,6 @@ namespace PSInfisicalAPI.Cmdlets
[Parameter]
public string OrganizationId { get; set; }
[Parameter]
public string ProjectId { get; set; }
[Parameter]
public string Environment { get; set; }
[Parameter(ParameterSetName = ParameterSetUniversalAuth)]
public string ClientId { get; set; }
@@ -62,9 +56,6 @@ namespace PSInfisicalAPI.Cmdlets
[Parameter(Mandatory = true, ParameterSetName = ParameterSetLdap)]
public SecureString Password { get; set; }
[Parameter]
public string SecretPath { get; set; } = "/";
[Parameter]
public string ApiVersion { get; set; } = "v4";
@@ -185,9 +176,6 @@ namespace PSInfisicalAPI.Cmdlets
PinnedApiVersion = apiVersionExplicitlyBound ? ApiVersion : null,
AuthType = authType,
OrganizationId = OrganizationId,
ProjectId = ProjectId,
Environment = Environment,
DefaultSecretPath = string.IsNullOrEmpty(SecretPath) ? "/" : SecretPath,
ConnectedAtUtc = DateTimeOffset.UtcNow,
ExpiresAtUtc = authResult.ExpiresAtUtc,
IsConnected = true,
@@ -215,8 +203,6 @@ namespace PSInfisicalAPI.Cmdlets
bool needsScan =
BaseUri == null ||
string.IsNullOrWhiteSpace(OrganizationId) ||
string.IsNullOrWhiteSpace(ProjectId) ||
string.IsNullOrWhiteSpace(Environment) ||
(tokenSet && (AccessToken == null || AccessToken.Length == 0)) ||
(universalSet && string.IsNullOrWhiteSpace(ClientId)) ||
(universalSet && (ClientSecret == null || ClientSecret.Length == 0));
@@ -242,8 +228,6 @@ namespace PSInfisicalAPI.Cmdlets
}
OrganizationId = InfisicalEnvironmentResolver.ResolveString("OrganizationId", InfisicalEnvironmentResolver.OrganizationIdPatterns, OrganizationId, Logger);
ProjectId = InfisicalEnvironmentResolver.ResolveString("ProjectId", InfisicalEnvironmentResolver.ProjectIdPatterns, ProjectId, Logger);
Environment = InfisicalEnvironmentResolver.ResolveString("Environment", InfisicalEnvironmentResolver.EnvironmentPatterns, Environment, Logger);
if (tokenSet)
{
@@ -255,15 +239,6 @@ namespace PSInfisicalAPI.Cmdlets
ClientSecret = InfisicalEnvironmentResolver.ResolveSecureString("ClientSecret", InfisicalEnvironmentResolver.ClientSecretPatterns, ClientSecret, Logger);
}
if (!MyInvocation.BoundParameters.ContainsKey("SecretPath"))
{
string resolvedPath = InfisicalEnvironmentResolver.ResolveString("SecretPath", InfisicalEnvironmentResolver.SecretPathPatterns, null, Logger);
if (!string.IsNullOrWhiteSpace(resolvedPath))
{
SecretPath = resolvedPath;
}
}
if (!MyInvocation.BoundParameters.ContainsKey("ApiVersion"))
{
string resolvedVersion = InfisicalEnvironmentResolver.ResolveString("ApiVersion", InfisicalEnvironmentResolver.ApiVersionPatterns, null, Logger);
@@ -280,8 +255,6 @@ namespace PSInfisicalAPI.Cmdlets
if (BaseUri == null) { missing.Add("BaseUri"); }
if (string.IsNullOrWhiteSpace(OrganizationId)) { missing.Add("OrganizationId"); }
if (string.IsNullOrWhiteSpace(ProjectId)) { missing.Add("ProjectId"); }
if (string.IsNullOrWhiteSpace(Environment)) { missing.Add("Environment"); }
if (string.Equals(ParameterSetName, ParameterSetToken, StringComparison.Ordinal))
{
@@ -18,9 +18,9 @@ namespace PSInfisicalAPI.Cmdlets
public string DestinationEnvironment { get; set; }
[Parameter] public string DestinationSecretPath { get; set; }
[Parameter] public string SourceEnvironment { get; set; }
[Parameter(Mandatory = true)] public string SourceEnvironment { get; set; }
[Parameter] public string SourceSecretPath { get; set; }
[Parameter] public string ProjectId { get; set; }
[Parameter(Mandatory = true)] public string ProjectId { get; set; }
[Parameter] public string ApiVersion { get; set; }
[Parameter] public SwitchParameter OverwriteExisting { get; set; }
[Parameter] public SwitchParameter CopySecretValue { get; set; }
@@ -35,9 +35,6 @@ namespace PSInfisicalAPI.Cmdlets
if (SecretId == null || SecretId.Length == 0) { return; }
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
string resolvedSourceEnv = ResolveEnvironment(connection, SourceEnvironment);
string resolvedSourcePath = ResolveSecretPath(connection, SourceSecretPath);
string resolvedApiVersion = ResolveApiVersion(connection, ApiVersion);
string target = string.Concat(SecretId.Length, " secret(s) -> ", DestinationEnvironment);
@@ -45,10 +42,10 @@ namespace PSInfisicalAPI.Cmdlets
InfisicalDuplicateSecretsRequest request = new InfisicalDuplicateSecretsRequest
{
ProjectId = resolvedProjectId,
SourceEnvironment = resolvedSourceEnv,
ProjectId = ProjectId,
SourceEnvironment = SourceEnvironment,
DestinationEnvironment = DestinationEnvironment,
SourceSecretPath = resolvedSourcePath,
SourceSecretPath = SourceSecretPath,
DestinationSecretPath = DestinationSecretPath,
SecretIds = SecretId,
ApiVersion = resolvedApiVersion,
@@ -0,0 +1,65 @@
using System;
using System.IO;
using System.Management.Automation;
using System.Text;
using PSInfisicalAPI.Models;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsData.Export, "InfisicalScepMdmProfile", SupportsShouldProcess = true)]
[OutputType(typeof(FileInfo))]
public sealed class ExportInfisicalScepMdmProfileCmdlet : InfisicalCmdletBase
{
private const string Component = "ExportInfisicalScepMdmProfileCmdlet";
[Parameter(Mandatory = true, ValueFromPipeline = true, Position = 0)]
[Alias("Profile", "ScepProfile")]
public InfisicalScepMdmProfile InputObject { get; set; }
[Parameter(Mandatory = true, Position = 1)]
public string Path { get; set; }
[Parameter] public SwitchParameter Force { get; set; }
[Parameter] public SwitchParameter PassThru { get; set; }
protected override void ProcessRecord()
{
try
{
if (InputObject == null) { throw new InvalidOperationException("InputObject is required."); }
string resolvedPath = SessionState.Path.GetUnresolvedProviderPathFromPSPath(Path);
if (File.Exists(resolvedPath) && !Force.IsPresent)
{
Logger.Warning(Component, string.Concat("File '", resolvedPath, "' already exists. Pass -Force to overwrite. Skipping export."));
return;
}
if (!ShouldProcess(resolvedPath, "Write SyncML SCEP MDM profile"))
{
return;
}
string directory = System.IO.Path.GetDirectoryName(resolvedPath);
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
Logger.Verbose(Component, string.Concat("Created directory '", directory, "'."));
}
string syncMl = InputObject.ToSyncMl();
File.WriteAllText(resolvedPath, syncMl, new UTF8Encoding(false));
Logger.Information(Component, string.Concat("Wrote SCEP MDM profile to '", resolvedPath, "' (UniqueId=", InputObject.UniqueId, ")."));
if (PassThru.IsPresent)
{
WriteObject(new FileInfo(resolvedPath));
}
}
catch (Exception exception)
{
ThrowTerminatingForException(Component, "ExportScepMdmProfile", exception);
}
}
}
}
@@ -0,0 +1,60 @@
using System;
using System.Management.Automation;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Models;
using PSInfisicalAPI.Pki;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsCommon.Get, "InfisicalCertificateApplication", DefaultParameterSetName = "List")]
[OutputType(typeof(InfisicalCertificateApplication))]
public sealed class GetInfisicalCertificateApplicationCmdlet : InfisicalCmdletBase
{
[Parameter(ParameterSetName = "ById", Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)]
[Alias("ApplicationId")]
public string Id { get; set; }
[Parameter(ParameterSetName = "ByName", Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)]
[Alias("Name")]
public string ApplicationName { get; set; }
[Parameter(Mandatory = true)] public string ProjectId { get; set; }
[Parameter(ParameterSetName = "List")] public int? Limit { get; set; }
[Parameter(ParameterSetName = "List")] public int? Offset { get; set; }
protected override void ProcessRecord()
{
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
InfisicalPkiClient client = new InfisicalPkiClient(HttpClient, Logger);
if (string.Equals(ParameterSetName, "ById", StringComparison.Ordinal))
{
InfisicalCertificateApplication app = client.GetCertificateApplication(connection, Id, ProjectId);
if (app != null) { WriteObject(app); }
return;
}
if (string.Equals(ParameterSetName, "ByName", StringComparison.Ordinal))
{
InfisicalCertificateApplication app = client.GetCertificateApplicationByName(connection, ApplicationName, ProjectId);
if (app != null) { WriteObject(app); }
return;
}
InfisicalCertificateApplication[] all = client.ListCertificateApplications(connection, ProjectId, Limit, Offset);
foreach (InfisicalCertificateApplication app in all)
{
WriteObject(app);
}
}
catch (Exception exception)
{
ThrowTerminatingForException("GetInfisicalCertificateApplicationCmdlet", "GetCertificateApplication", exception);
}
}
}
}
@@ -0,0 +1,42 @@
using System;
using System.Management.Automation;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Models;
using PSInfisicalAPI.Pki;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsCommon.Get, "InfisicalCertificateApplicationEnrollment")]
[OutputType(typeof(InfisicalCertificateApplicationEnrollment))]
public sealed class GetInfisicalCertificateApplicationEnrollmentCmdlet : InfisicalCmdletBase
{
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)]
[Alias("Id")]
public string ApplicationId { get; set; }
[Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true)]
[Alias("CertificateProfileId")]
public string ProfileId { get; set; }
[Parameter(Mandatory = true)] public string ProjectId { get; set; }
protected override void ProcessRecord()
{
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
InfisicalPkiClient client = new InfisicalPkiClient(HttpClient, Logger);
InfisicalCertificateApplicationEnrollment enrollment = client.GetCertificateApplicationEnrollment(connection, ApplicationId, ProfileId, ProjectId);
if (enrollment != null)
{
WriteObject(enrollment);
}
}
catch (Exception exception)
{
ThrowTerminatingForException("GetInfisicalCertificateApplicationEnrollmentCmdlet", "GetCertificateApplicationEnrollment", exception);
}
}
}
}
@@ -14,19 +14,22 @@ namespace PSInfisicalAPI.Cmdlets
[Alias("Id")]
public string CaId { get; set; }
[Parameter] public string ProjectId { get; set; }
[Parameter(Mandatory = true)] public string ProjectId { get; set; }
[Parameter(ParameterSetName = "List")]
[ValidateSet("Internal", "Acme", "Any")]
public string Kind { get; set; } = "Internal";
protected override void ProcessRecord()
{
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
InfisicalPkiClient client = new InfisicalPkiClient(HttpClient, Logger);
if (string.Equals(ParameterSetName, "ById", StringComparison.Ordinal))
{
InfisicalCertificateAuthority ca = client.GetInternalCertificateAuthority(connection, CaId, resolvedProjectId);
InfisicalCertificateAuthority ca = client.GetInternalCertificateAuthority(connection, CaId, ProjectId);
if (ca != null)
{
WriteObject(ca);
@@ -35,7 +38,20 @@ namespace PSInfisicalAPI.Cmdlets
return;
}
InfisicalCertificateAuthority[] all = client.ListInternalCertificateAuthorities(connection, resolvedProjectId);
InfisicalCertificateAuthority[] all;
if (string.Equals(Kind, "Internal", StringComparison.OrdinalIgnoreCase))
{
all = client.ListInternalCertificateAuthorities(connection, ProjectId);
}
else
{
all = client.ListAllCertificateAuthorities(connection, ProjectId);
if (string.Equals(Kind, "Acme", StringComparison.OrdinalIgnoreCase))
{
all = FilterByType(all, "acme");
}
}
foreach (InfisicalCertificateAuthority ca in all)
{
WriteObject(ca);
@@ -46,5 +62,20 @@ namespace PSInfisicalAPI.Cmdlets
ThrowTerminatingForException("GetInfisicalCertificateAuthorityCmdlet", "GetCertificateAuthority", exception);
}
}
private static InfisicalCertificateAuthority[] FilterByType(InfisicalCertificateAuthority[] source, string type)
{
if (source == null || source.Length == 0) { return Array.Empty<InfisicalCertificateAuthority>(); }
System.Collections.Generic.List<InfisicalCertificateAuthority> kept = new System.Collections.Generic.List<InfisicalCertificateAuthority>();
foreach (InfisicalCertificateAuthority ca in source)
{
if (ca != null && string.Equals(ca.Type, type, StringComparison.OrdinalIgnoreCase))
{
kept.Add(ca);
}
}
return kept.ToArray();
}
}
}
@@ -0,0 +1,91 @@
using System;
using System.Management.Automation;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Models;
using PSInfisicalAPI.Pki;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsCommon.Get, "InfisicalCertificate", DefaultParameterSetName = "List")]
[OutputType(typeof(InfisicalCertificate))]
public sealed class GetInfisicalCertificateCmdlet : InfisicalCmdletBase
{
[Parameter(ParameterSetName = "Single", Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)]
[Alias("Id", "Identifier")]
public string SerialNumber { get; set; }
[Parameter(ParameterSetName = "List")] public SwitchParameter List { get; set; }
[Parameter(ParameterSetName = "List", Mandatory = true)] public string ProjectId { get; set; }
[Parameter(ParameterSetName = "List")] public string CommonName { get; set; }
[Parameter(ParameterSetName = "List")] public string FriendlyName { get; set; }
[Parameter(ParameterSetName = "List")] public string Status { get; set; }
[Parameter(ParameterSetName = "List")] public string[] CaId { get; set; }
[Parameter(ParameterSetName = "List")] public int? Limit { get; set; }
[Parameter(ParameterSetName = "List")] public int? Offset { get; set; }
[Parameter(ParameterSetName = "List")] public SwitchParameter NoAutoPage { get; set; }
protected override void ProcessRecord()
{
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
InfisicalPkiClient client = new InfisicalPkiClient(HttpClient, Logger);
if (string.Equals(ParameterSetName, "Single", StringComparison.Ordinal))
{
InfisicalCertificate cert = client.RetrieveCertificate(connection, SerialNumber);
if (cert != null)
{
WriteObject(cert);
}
return;
}
InfisicalCertificateSearchQuery query = new InfisicalCertificateSearchQuery
{
ProjectId = ProjectId,
CommonName = CommonName,
FriendlyName = FriendlyName,
Status = Status,
CaIds = CaId,
Limit = Limit ?? 100,
Offset = Offset ?? 0
};
int requestedLimit = query.Limit ?? 100;
int emitted = 0;
while (true)
{
InfisicalCertificateSearchResult page = client.SearchCertificates(connection, query);
if (page == null || page.Certificates == null || page.Certificates.Length == 0)
{
break;
}
foreach (InfisicalCertificate cert in page.Certificates)
{
WriteObject(cert);
emitted++;
}
if (NoAutoPage.IsPresent || page.Certificates.Length < requestedLimit)
{
break;
}
if (page.TotalCount > 0 && emitted >= page.TotalCount)
{
break;
}
query.Offset = (query.Offset ?? 0) + page.Certificates.Length;
}
}
catch (Exception exception)
{
ThrowTerminatingForException("GetInfisicalCertificateCmdlet", "GetCertificate", exception);
}
}
}
}
@@ -0,0 +1,53 @@
using System;
using System.Management.Automation;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Models;
using PSInfisicalAPI.Pki;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsCommon.Get, "InfisicalCertificatePolicy", DefaultParameterSetName = "List")]
[OutputType(typeof(InfisicalCertificatePolicy))]
public sealed class GetInfisicalCertificatePolicyCmdlet : InfisicalCmdletBase
{
[Parameter(ParameterSetName = "ById", Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)]
[Alias("Id", "CertificatePolicyId")]
public string PolicyId { get; set; }
[Parameter(Mandatory = true)] public string ProjectId { get; set; }
[Parameter(ParameterSetName = "List")] public int? Limit { get; set; }
[Parameter(ParameterSetName = "List")] public int? Offset { get; set; }
protected override void ProcessRecord()
{
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
InfisicalPkiClient client = new InfisicalPkiClient(HttpClient, Logger);
if (string.Equals(ParameterSetName, "ById", StringComparison.Ordinal))
{
InfisicalCertificatePolicy policy = client.GetCertificatePolicy(connection, PolicyId, ProjectId);
if (policy != null)
{
WriteObject(policy);
}
return;
}
InfisicalCertificatePolicy[] all = client.ListCertificatePolicies(connection, ProjectId, Limit, Offset);
foreach (InfisicalCertificatePolicy policy in all)
{
WriteObject(policy);
}
}
catch (Exception exception)
{
ThrowTerminatingForException("GetInfisicalCertificatePolicyCmdlet", "GetCertificatePolicy", exception);
}
}
}
}
@@ -0,0 +1,56 @@
using System;
using System.Management.Automation;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Models;
using PSInfisicalAPI.Pki;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsCommon.Get, "InfisicalCertificateProfile", DefaultParameterSetName = "List")]
[OutputType(typeof(InfisicalCertificateProfile))]
public sealed class GetInfisicalCertificateProfileCmdlet : InfisicalCmdletBase
{
[Parameter(ParameterSetName = "ById", Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)]
[Alias("Id", "CertificateProfileId")]
public string ProfileId { get; set; }
[Parameter(Mandatory = true)] public string ProjectId { get; set; }
[Parameter(ParameterSetName = "List")] public int? Limit { get; set; }
[Parameter(ParameterSetName = "List")] public int? Offset { get; set; }
[Parameter(ParameterSetName = "List")] public SwitchParameter IncludeConfigs { get; set; }
protected override void ProcessRecord()
{
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
InfisicalPkiClient client = new InfisicalPkiClient(HttpClient, Logger);
if (string.Equals(ParameterSetName, "ById", StringComparison.Ordinal))
{
InfisicalCertificateProfile profile = client.GetCertificateProfile(connection, ProfileId, ProjectId);
if (profile != null)
{
WriteObject(profile);
}
return;
}
bool? includeConfigs = MyInvocation.BoundParameters.ContainsKey("IncludeConfigs") ? (bool?)IncludeConfigs.IsPresent : null;
InfisicalCertificateProfile[] all = client.ListCertificateProfiles(connection, ProjectId, Limit, Offset, includeConfigs);
foreach (InfisicalCertificateProfile profile in all)
{
WriteObject(profile);
}
}
catch (Exception exception)
{
ThrowTerminatingForException("GetInfisicalCertificateProfileCmdlet", "GetCertificateProfile", exception);
}
}
}
}
@@ -6,32 +6,45 @@ using PSInfisicalAPI.Models;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsCommon.Get, "InfisicalEnvironment")]
[Cmdlet(VerbsCommon.Get, "InfisicalEnvironment", DefaultParameterSetName = "List")]
[OutputType(typeof(InfisicalEnvironment))]
public sealed class GetInfisicalEnvironmentCmdlet : InfisicalCmdletBase
{
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)]
[Parameter(ParameterSetName = "Single", Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)]
[Alias("Slug", "Id", "Environment")]
public string EnvironmentSlugOrId { get; set; }
[Parameter] public string ProjectId { get; set; }
[Parameter(Mandatory = true)] public string ProjectId { get; set; }
[Parameter(ParameterSetName = "List")] public SwitchParameter List { get; set; }
protected override void ProcessRecord()
{
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
InfisicalEnvironmentClient client = new InfisicalEnvironmentClient(HttpClient, Logger);
InfisicalEnvironment env = client.Retrieve(connection, resolvedProjectId, EnvironmentSlugOrId);
if (string.Equals(ParameterSetName, "Single", StringComparison.Ordinal))
{
InfisicalEnvironment env = client.Retrieve(connection, ProjectId, EnvironmentSlugOrId);
if (env != null)
{
WriteObject(env);
}
return;
}
InfisicalEnvironment[] envs = client.List(connection, ProjectId);
foreach (InfisicalEnvironment env in envs)
{
WriteObject(env);
}
}
catch (Exception exception)
{
ThrowTerminatingForException("GetInfisicalEnvironmentCmdlet", "RetrieveEnvironment", exception);
ThrowTerminatingForException("GetInfisicalEnvironmentCmdlet", "GetEnvironment", exception);
}
}
}
@@ -1,34 +0,0 @@
using System;
using System.Management.Automation;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Environments;
using PSInfisicalAPI.Models;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsCommon.Get, "InfisicalEnvironments")]
[OutputType(typeof(InfisicalEnvironment))]
public sealed class GetInfisicalEnvironmentsCmdlet : InfisicalCmdletBase
{
[Parameter] public string ProjectId { get; set; }
protected override void ProcessRecord()
{
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
InfisicalEnvironmentClient client = new InfisicalEnvironmentClient(HttpClient, Logger);
InfisicalEnvironment[] envs = client.List(connection, resolvedProjectId);
foreach (InfisicalEnvironment env in envs)
{
WriteObject(env);
}
}
catch (Exception exception)
{
ThrowTerminatingForException("GetInfisicalEnvironmentsCmdlet", "ListEnvironments", exception);
}
}
}
}
@@ -6,36 +6,47 @@ using PSInfisicalAPI.Models;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsCommon.Get, "InfisicalFolder")]
[Cmdlet(VerbsCommon.Get, "InfisicalFolder", DefaultParameterSetName = "List")]
[OutputType(typeof(InfisicalFolder))]
public sealed class GetInfisicalFolderCmdlet : InfisicalCmdletBase
{
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)]
[Parameter(ParameterSetName = "Single", Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)]
[Alias("Name", "Id")]
public string FolderNameOrId { get; set; }
[Parameter] public string ProjectId { get; set; }
[Parameter] public string Environment { get; set; }
[Parameter(Mandatory = true)] public string ProjectId { get; set; }
[Parameter(Mandatory = true)] public string Environment { get; set; }
[Parameter] public string Path { get; set; }
[Parameter(ParameterSetName = "List")] public SwitchParameter List { get; set; }
protected override void ProcessRecord()
{
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
string resolvedEnvironment = ResolveEnvironment(connection, Environment);
string resolvedPath = ResolveSecretPath(connection, Path);
InfisicalFolderClient client = new InfisicalFolderClient(HttpClient, Logger);
InfisicalFolder folder = client.Retrieve(connection, resolvedProjectId, resolvedEnvironment, resolvedPath, FolderNameOrId);
if (string.Equals(ParameterSetName, "Single", StringComparison.Ordinal))
{
InfisicalFolder folder = client.Retrieve(connection, ProjectId, Environment, Path, FolderNameOrId);
if (folder != null)
{
WriteObject(folder);
}
return;
}
InfisicalFolder[] folders = client.List(connection, ProjectId, Environment, Path);
foreach (InfisicalFolder folder in folders)
{
WriteObject(folder);
}
}
catch (Exception exception)
{
ThrowTerminatingForException("GetInfisicalFolderCmdlet", "RetrieveFolder", exception);
ThrowTerminatingForException("GetInfisicalFolderCmdlet", "GetFolder", exception);
}
}
}
@@ -1,38 +0,0 @@
using System;
using System.Management.Automation;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Folders;
using PSInfisicalAPI.Models;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsCommon.Get, "InfisicalFolders")]
[OutputType(typeof(InfisicalFolder))]
public sealed class GetInfisicalFoldersCmdlet : InfisicalCmdletBase
{
[Parameter] public string ProjectId { get; set; }
[Parameter] public string Environment { get; set; }
[Parameter] public string Path { get; set; }
protected override void ProcessRecord()
{
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
string resolvedEnvironment = ResolveEnvironment(connection, Environment);
string resolvedPath = ResolveSecretPath(connection, Path);
InfisicalFolderClient client = new InfisicalFolderClient(HttpClient, Logger);
InfisicalFolder[] folders = client.List(connection, resolvedProjectId, resolvedEnvironment, resolvedPath);
foreach (InfisicalFolder folder in folders)
{
WriteObject(folder);
}
}
catch (Exception exception)
{
ThrowTerminatingForException("GetInfisicalFoldersCmdlet", "ListFolders", exception);
}
}
}
}
@@ -0,0 +1,49 @@
using System;
using System.Management.Automation;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Models;
using PSInfisicalAPI.Pki;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsCommon.Get, "InfisicalPkiSubscriber", DefaultParameterSetName = "List")]
[OutputType(typeof(InfisicalPkiSubscriber))]
public sealed class GetInfisicalPkiSubscriberCmdlet : InfisicalCmdletBase
{
[Parameter(ParameterSetName = "ByName", Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)]
[Alias("SubscriberName", "Slug")]
public string Name { get; set; }
[Parameter(Mandatory = true)] public string ProjectId { get; set; }
protected override void ProcessRecord()
{
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
InfisicalPkiClient client = new InfisicalPkiClient(HttpClient, Logger);
if (string.Equals(ParameterSetName, "ByName", StringComparison.Ordinal))
{
InfisicalPkiSubscriber subscriber = client.GetPkiSubscriber(connection, Name, ProjectId);
if (subscriber != null)
{
WriteObject(subscriber);
}
return;
}
InfisicalPkiSubscriber[] all = client.ListPkiSubscribers(connection, ProjectId);
foreach (InfisicalPkiSubscriber subscriber in all)
{
WriteObject(subscriber);
}
}
catch (Exception exception)
{
ThrowTerminatingForException("GetInfisicalPkiSubscriberCmdlet", "GetPkiSubscriber", exception);
}
}
}
}
@@ -6,30 +6,49 @@ using PSInfisicalAPI.Projects;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsCommon.Get, "InfisicalProject")]
[Cmdlet(VerbsCommon.Get, "InfisicalProject", DefaultParameterSetName = "List")]
[OutputType(typeof(InfisicalProject))]
public sealed class GetInfisicalProjectCmdlet : InfisicalCmdletBase
{
[Parameter(ValueFromPipelineByPropertyName = true, Position = 0)]
[Parameter(ParameterSetName = "Single", Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)]
[Alias("Id")]
public string ProjectId { get; set; }
[Parameter(ParameterSetName = "List")] public SwitchParameter List { get; set; }
[Parameter(ParameterSetName = "List")]
[ValidateSet("secret-manager", "cert-manager", "kms", "ssh", "secret-scanning", "pam", "ai")]
public string Type { get; set; }
[Parameter(ParameterSetName = "List")] public SwitchParameter IncludeRoles { get; set; }
protected override void ProcessRecord()
{
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
InfisicalProjectClient client = new InfisicalProjectClient(HttpClient, Logger);
InfisicalProject project = client.Retrieve(connection, resolvedProjectId);
if (string.Equals(ParameterSetName, "Single", StringComparison.Ordinal))
{
InfisicalProject project = client.Retrieve(connection, ProjectId);
if (project != null)
{
WriteObject(project);
}
return;
}
InfisicalProject[] projects = client.List(connection, Type, IncludeRoles.IsPresent);
foreach (InfisicalProject project in projects)
{
WriteObject(project);
}
}
catch (Exception exception)
{
ThrowTerminatingForException("GetInfisicalProjectCmdlet", "RetrieveProject", exception);
ThrowTerminatingForException("GetInfisicalProjectCmdlet", "GetProject", exception);
}
}
}
@@ -1,32 +0,0 @@
using System;
using System.Management.Automation;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Models;
using PSInfisicalAPI.Projects;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsCommon.Get, "InfisicalProjects")]
[OutputType(typeof(InfisicalProject))]
public sealed class GetInfisicalProjectsCmdlet : InfisicalCmdletBase
{
protected override void ProcessRecord()
{
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
InfisicalProjectClient client = new InfisicalProjectClient(HttpClient, Logger);
InfisicalProject[] projects = client.List(connection);
foreach (InfisicalProject project in projects)
{
WriteObject(project);
}
}
catch (Exception exception)
{
ThrowTerminatingForException("GetInfisicalProjectsCmdlet", "ListProjects", exception);
}
}
}
}
@@ -0,0 +1,241 @@
using System;
using System.Globalization;
using System.Management.Automation;
using System.Net;
using System.Runtime.InteropServices;
using System.Security;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Models;
using PSInfisicalAPI.Pki;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsCommon.Get, "InfisicalScepMdmProfile", DefaultParameterSetName = "FromEnrollment")]
[OutputType(typeof(InfisicalScepMdmProfile))]
public sealed class GetInfisicalScepMdmProfileCmdlet : InfisicalCmdletBase
{
private const string Component = "GetInfisicalScepMdmProfileCmdlet";
[Parameter(ParameterSetName = "FromEnrollment", Mandatory = true, ValueFromPipeline = true, Position = 0)]
[Alias("Enrollment")]
public InfisicalCertificateApplicationEnrollment EnrollmentObject { get; set; }
[Parameter(ParameterSetName = "FromProfile", Mandatory = true, ValueFromPipeline = true, Position = 0)]
[Alias("Profile", "CertificateProfile")]
public InfisicalCertificateProfile InputObject { get; set; }
[Parameter(ParameterSetName = "FromProfile", Mandatory = true)]
[Alias("AppId")]
public string ApplicationId { get; set; }
[Parameter(ParameterSetName = "FromEnrollment")]
[Parameter(ParameterSetName = "FromProfile")]
[Parameter(ParameterSetName = "Manual", Mandatory = true)]
public SecureString Challenge { get; set; }
[Parameter(ParameterSetName = "Manual", Mandatory = true)]
[Parameter(ParameterSetName = "FromProfile")]
[Parameter(ParameterSetName = "FromEnrollment")]
public string ServerUrl { get; set; }
[Parameter] public string UniqueId { get; set; }
[Parameter]
[ValidateSet("Device", "User")]
public string Scope { get; set; } = "Device";
[Parameter] public string SubjectName { get; set; }
[Parameter] public string SubjectAlternativeNames { get; set; }
[Parameter] public string EkuMapping { get; set; }
[Parameter] public int? KeyUsage { get; set; }
[Parameter] public int? KeyLength { get; set; }
[Parameter] public string KeyAlgorithm { get; set; }
[Parameter] public string HashAlgorithm { get; set; }
[Parameter] public int? KeyProtection { get; set; }
[Parameter] public string ContainerName { get; set; }
[Parameter] public string ValidPeriod { get; set; }
[Parameter] public int? ValidPeriodUnits { get; set; }
[Parameter] public int? RetryCount { get; set; }
[Parameter] public int? RetryDelay { get; set; }
[Parameter] public string TemplateName { get; set; }
[Parameter] public string CAThumbprint { get; set; }
[Parameter] public string CustomTextToShowInPrompt { get; set; }
protected override void ProcessRecord()
{
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
if (string.Equals(ParameterSetName, "FromEnrollment", StringComparison.Ordinal))
{
WriteObject(BuildFromEnrollment(connection));
return;
}
if (string.Equals(ParameterSetName, "FromProfile", StringComparison.Ordinal))
{
WriteObject(BuildFromProfile(connection));
return;
}
WriteObject(BuildManual(connection));
}
catch (Exception exception)
{
ThrowTerminatingForException(Component, "GetScepMdmProfile", exception);
}
}
private InfisicalScepMdmProfile BuildFromEnrollment(InfisicalConnection connection)
{
if (EnrollmentObject == null) { throw new InvalidOperationException("EnrollmentObject is required."); }
if (string.IsNullOrEmpty(EnrollmentObject.ApplicationId)) { throw new InvalidOperationException("EnrollmentObject.ApplicationId is required."); }
if (string.IsNullOrEmpty(EnrollmentObject.ProfileId)) { throw new InvalidOperationException("EnrollmentObject.ProfileId is required."); }
InfisicalCertificateApplicationScepEnrollment scep = EnrollmentObject.Scep;
if (scep == null) { throw new InvalidOperationException("Enrollment does not have SCEP configured."); }
string resolvedServerUrl = FirstNonEmpty(ServerUrl, scep.ScepEndpointUrl, BuildDefaultServerUrl(connection, EnrollmentObject.ApplicationId, EnrollmentObject.ProfileId));
string resolvedUniqueId = !string.IsNullOrEmpty(UniqueId) ? UniqueId : SanitizeForCspId(EnrollmentObject.ProfileId);
string resolvedThumbprint = !string.IsNullOrEmpty(CAThumbprint) ? CAThumbprint : scep.RaCertificateThumbprint;
string resolvedChallenge = ResolveChallengeFromEnrollment(connection, scep);
InfisicalScepMdmProfile result = NewProfileShell(resolvedUniqueId, resolvedServerUrl, resolvedChallenge, resolvedThumbprint, null, null);
result.SourceProfileId = EnrollmentObject.ProfileId;
Logger.Verbose(Component, string.Concat("Built SCEP MDM profile from enrollment for application '", EnrollmentObject.ApplicationId, "' / profile '", EnrollmentObject.ProfileId, "' targeting ", result.ServerUrl, " (UniqueId=", result.UniqueId, ", Scope=", result.Scope, ", ChallengeType=", scep.ChallengeType ?? "<unknown>", ")."));
return result;
}
private InfisicalScepMdmProfile BuildFromProfile(InfisicalConnection connection)
{
if (InputObject == null) { throw new InvalidOperationException("InputObject is required."); }
if (string.IsNullOrEmpty(InputObject.Id)) { throw new InvalidOperationException("InputObject.Id is required."); }
if (string.IsNullOrEmpty(ApplicationId)) { throw new InvalidOperationException("ApplicationId is required when binding by certificate profile."); }
if (Challenge == null) { throw new InvalidOperationException("Challenge is required when building from a certificate profile."); }
string resolvedServerUrl = !string.IsNullOrEmpty(ServerUrl) ? ServerUrl : BuildDefaultServerUrl(connection, ApplicationId, InputObject.Id);
string resolvedUniqueId = !string.IsNullOrEmpty(UniqueId) ? UniqueId : SanitizeForCspId(!string.IsNullOrEmpty(InputObject.Slug) ? InputObject.Slug : InputObject.Id);
InfisicalCertificateProfileDefaults defaults = InputObject.Defaults;
string resolvedKeyAlgorithm = !string.IsNullOrEmpty(KeyAlgorithm) ? KeyAlgorithm : MapKeyAlgorithm(defaults != null ? defaults.KeyAlgorithm : null);
string resolvedEku = !string.IsNullOrEmpty(EkuMapping) ? EkuMapping : JoinEkuOids(defaults != null ? defaults.ExtendedKeyUsages : null);
InfisicalScepMdmProfile result = NewProfileShell(resolvedUniqueId, resolvedServerUrl, SecureStringToPlainText(Challenge), CAThumbprint, resolvedKeyAlgorithm, resolvedEku);
result.SourceProfileId = InputObject.Id;
result.SourceProfileSlug = InputObject.Slug;
Logger.Verbose(Component, string.Concat("Built SCEP MDM profile for source profile '", InputObject.Slug ?? InputObject.Id, "' targeting ", result.ServerUrl, " (UniqueId=", result.UniqueId, ", Scope=", result.Scope, ")."));
return result;
}
private InfisicalScepMdmProfile BuildManual(InfisicalConnection connection)
{
if (string.IsNullOrEmpty(UniqueId)) { throw new InvalidOperationException("UniqueId is required in Manual mode."); }
string resolvedChallenge = SecureStringToPlainText(Challenge);
InfisicalScepMdmProfile result = NewProfileShell(UniqueId, ServerUrl, resolvedChallenge, CAThumbprint, KeyAlgorithm, EkuMapping);
Logger.Verbose(Component, string.Concat("Built SCEP MDM profile in Manual mode targeting ", result.ServerUrl, " (UniqueId=", result.UniqueId, ", Scope=", result.Scope, ")."));
return result;
}
private InfisicalScepMdmProfile NewProfileShell(string uniqueId, string serverUrl, string challenge, string thumbprint, string keyAlgorithm, string ekuMapping)
{
return new InfisicalScepMdmProfile
{
UniqueId = uniqueId,
Scope = Scope,
ServerUrl = serverUrl,
Challenge = challenge,
SubjectName = SubjectName,
SubjectAlternativeNames = SubjectAlternativeNames,
EkuMapping = ekuMapping,
KeyUsage = KeyUsage,
KeyLength = KeyLength,
KeyAlgorithm = keyAlgorithm,
HashAlgorithm = HashAlgorithm,
KeyProtection = KeyProtection,
ContainerName = ContainerName,
ValidPeriod = ValidPeriod,
ValidPeriodUnits = ValidPeriodUnits,
RetryCount = RetryCount,
RetryDelay = RetryDelay,
TemplateName = TemplateName,
CAThumbprint = thumbprint,
CustomTextToShowInPrompt = CustomTextToShowInPrompt
};
}
private string ResolveChallengeFromEnrollment(InfisicalConnection connection, InfisicalCertificateApplicationScepEnrollment scep)
{
if (Challenge != null) { return SecureStringToPlainText(Challenge); }
string challengeType = scep.ChallengeType ?? string.Empty;
if (string.Equals(challengeType, "dynamic", StringComparison.OrdinalIgnoreCase))
{
InfisicalPkiClient client = new InfisicalPkiClient(HttpClient, Logger);
Logger.Verbose(Component, "Minting SCEP dynamic challenge for enrollment.");
return client.GenerateScepDynamicChallenge(connection, EnrollmentObject.ApplicationId, EnrollmentObject.ProfileId);
}
throw new InvalidOperationException(string.Concat("Enrollment uses challengeType '", challengeType, "'. Supply -Challenge with the configured static challenge password."));
}
private static string BuildDefaultServerUrl(InfisicalConnection connection, string applicationId, string profileId)
{
if (connection == null || connection.BaseUri == null) { throw new InvalidOperationException("Active Infisical connection is required to derive ServerUrl."); }
string baseUrl = connection.BaseUri.GetLeftPart(UriPartial.Authority);
return string.Concat(baseUrl, "/scep/applications/", applicationId, "/profiles/", profileId, "/pkiclient.exe");
}
private static string FirstNonEmpty(params string[] values)
{
if (values == null) { return null; }
foreach (string value in values) { if (!string.IsNullOrEmpty(value)) { return value; } }
return null;
}
private static string SanitizeForCspId(string input)
{
if (string.IsNullOrEmpty(input)) { return "Infisical"; }
char[] buffer = new char[input.Length];
for (int i = 0; i < input.Length; i++)
{
char c = input[i];
buffer[i] = (char.IsLetterOrDigit(c) || c == '-' || c == '_') ? c : '_';
}
return new string(buffer);
}
private static string MapKeyAlgorithm(string fromDefaults)
{
if (string.IsNullOrEmpty(fromDefaults)) { return null; }
if (fromDefaults.IndexOf("rsa", StringComparison.OrdinalIgnoreCase) >= 0) { return "RSA"; }
if (fromDefaults.IndexOf("ec", StringComparison.OrdinalIgnoreCase) >= 0) { return "ECDSA_P256"; }
return null;
}
private static string JoinEkuOids(string[] values)
{
if (values == null || values.Length == 0) { return null; }
System.Text.StringBuilder sb = new System.Text.StringBuilder();
bool first = true;
foreach (string v in values)
{
if (string.IsNullOrEmpty(v)) { continue; }
if (!first) { sb.Append('+'); }
sb.Append(v);
first = false;
}
return sb.Length > 0 ? sb.ToString() : null;
}
private static string SecureStringToPlainText(SecureString value)
{
if (value == null) { return null; }
IntPtr ptr = Marshal.SecureStringToGlobalAllocUnicode(value);
try { return Marshal.PtrToStringUni(ptr); }
finally { if (ptr != IntPtr.Zero) { Marshal.ZeroFreeGlobalAllocUnicode(ptr); } }
}
}
}
@@ -1,4 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Management.Automation;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Models;
@@ -6,35 +8,45 @@ using PSInfisicalAPI.Secrets;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsCommon.Get, "InfisicalSecret")]
[Cmdlet(VerbsCommon.Get, "InfisicalSecret", DefaultParameterSetName = "List")]
[OutputType(typeof(InfisicalSecret))]
public sealed class GetInfisicalSecretCmdlet : InfisicalCmdletBase
{
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)]
[Parameter(ParameterSetName = "Single", Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)]
public string SecretName { get; set; }
[Parameter] public string ProjectId { get; set; }
[Parameter] public string Environment { get; set; }
[Parameter(Mandatory = true)] public string ProjectId { get; set; }
[Parameter(Mandatory = true)] public string Environment { get; set; }
[Parameter] public string SecretPath { get; set; }
[Parameter] public string ApiVersion { get; set; }
[Parameter] public int? Version { get; set; }
[Parameter] public InfisicalSecretType Type { get; set; } = InfisicalSecretType.Shared;
[Parameter] public SwitchParameter ViewSecretValue { get; set; } = SwitchParameter.Present;
[Parameter] public SwitchParameter ExpandSecretReferences { get; set; }
[Parameter] public SwitchParameter IncludeImports { get; set; }
[Parameter(ParameterSetName = "Single")] public int? Version { get; set; }
[Parameter(ParameterSetName = "Single")] public InfisicalSecretType Type { get; set; } = InfisicalSecretType.Shared;
[Parameter(ParameterSetName = "List")] public SwitchParameter List { get; set; }
[Parameter(ParameterSetName = "List")] public SwitchParameter Recursive { get; set; }
[Parameter(ParameterSetName = "List")] public SwitchParameter IncludePersonalOverrides { get; set; }
[Parameter(ParameterSetName = "List")] public Hashtable MetadataFilter { get; set; }
[Parameter(ParameterSetName = "List")] public string[] TagSlugs { get; set; }
protected override void ProcessRecord()
{
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
InfisicalSecretsClient client = new InfisicalSecretsClient(HttpClient, Logger);
if (string.Equals(ParameterSetName, "Single", StringComparison.Ordinal))
{
InfisicalRetrieveSecretQuery query = new InfisicalRetrieveSecretQuery
{
SecretName = SecretName,
ProjectId = ResolveProjectId(connection, ProjectId),
Environment = ResolveEnvironment(connection, Environment),
SecretPath = ResolveSecretPath(connection, SecretPath),
ProjectId = ProjectId,
Environment = Environment,
SecretPath = SecretPath,
ApiVersion = ResolveApiVersion(connection, ApiVersion),
Version = Version,
Type = Type.ToString(),
@@ -43,18 +55,54 @@ namespace PSInfisicalAPI.Cmdlets
IncludeImports = IncludeImports.IsPresent
};
InfisicalSecretsClient client = new InfisicalSecretsClient(HttpClient, Logger);
InfisicalSecret secret = client.Retrieve(connection, query);
if (secret != null)
{
WriteObject(secret);
}
return;
}
InfisicalListSecretsQuery listQuery = new InfisicalListSecretsQuery
{
ProjectId = ProjectId,
Environment = Environment,
SecretPath = SecretPath,
ApiVersion = ResolveApiVersion(connection, ApiVersion),
Recursive = Recursive.IsPresent,
IncludeImports = IncludeImports.IsPresent,
IncludePersonalOverrides = IncludePersonalOverrides.IsPresent,
ExpandSecretReferences = ExpandSecretReferences.IsPresent,
ViewSecretValue = ViewSecretValue.IsPresent,
MetadataFilter = ToStringDictionary(MetadataFilter),
TagSlugs = TagSlugs
};
InfisicalSecret[] secrets = client.List(connection, listQuery);
foreach (InfisicalSecret secret in secrets)
{
WriteObject(secret);
}
}
catch (Exception exception)
{
ThrowTerminatingForException("GetInfisicalSecretCmdlet", "RetrieveSecret", exception);
}
ThrowTerminatingForException("GetInfisicalSecretCmdlet", "GetSecret", exception);
}
}
private static Dictionary<string, string> ToStringDictionary(Hashtable hashtable)
{
if (hashtable == null) { return null; }
Dictionary<string, string> result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (DictionaryEntry entry in hashtable)
{
if (entry.Key == null) { continue; }
result[entry.Key.ToString()] = entry.Value != null ? entry.Value.ToString() : null;
}
return result;
}
}
}
@@ -1,76 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Management.Automation;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Models;
using PSInfisicalAPI.Secrets;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsCommon.Get, "InfisicalSecrets")]
[OutputType(typeof(InfisicalSecret))]
public sealed class GetInfisicalSecretsCmdlet : InfisicalCmdletBase
{
[Parameter] public string ProjectId { get; set; }
[Parameter] public string Environment { get; set; }
[Parameter] public string SecretPath { get; set; }
[Parameter] public string ApiVersion { get; set; }
[Parameter] public SwitchParameter Recursive { get; set; }
[Parameter] public SwitchParameter IncludeImports { get; set; }
[Parameter] public SwitchParameter IncludePersonalOverrides { get; set; }
[Parameter] public SwitchParameter ExpandSecretReferences { get; set; }
[Parameter] public SwitchParameter ViewSecretValue { get; set; } = SwitchParameter.Present;
[Parameter] public Hashtable MetadataFilter { get; set; }
[Parameter] public string[] TagSlugs { get; set; }
protected override void ProcessRecord()
{
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
InfisicalListSecretsQuery query = new InfisicalListSecretsQuery
{
ProjectId = ResolveProjectId(connection, ProjectId),
Environment = ResolveEnvironment(connection, Environment),
SecretPath = ResolveSecretPath(connection, SecretPath),
ApiVersion = ResolveApiVersion(connection, ApiVersion),
Recursive = Recursive.IsPresent,
IncludeImports = IncludeImports.IsPresent,
IncludePersonalOverrides = IncludePersonalOverrides.IsPresent,
ExpandSecretReferences = ExpandSecretReferences.IsPresent,
ViewSecretValue = ViewSecretValue.IsPresent,
MetadataFilter = ToStringDictionary(MetadataFilter),
TagSlugs = TagSlugs
};
InfisicalSecretsClient client = new InfisicalSecretsClient(HttpClient, Logger);
InfisicalSecret[] secrets = client.List(connection, query);
foreach (InfisicalSecret secret in secrets)
{
WriteObject(secret);
}
}
catch (Exception exception)
{
ThrowTerminatingForException("GetInfisicalSecretsCmdlet", "RetrieveSecrets", exception);
}
}
private static Dictionary<string, string> ToStringDictionary(Hashtable hashtable)
{
if (hashtable == null) { return null; }
Dictionary<string, string> result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (DictionaryEntry entry in hashtable)
{
if (entry.Key == null) { continue; }
result[entry.Key.ToString()] = entry.Value != null ? entry.Value.ToString() : null;
}
return result;
}
}
}
@@ -6,32 +6,45 @@ using PSInfisicalAPI.Tags;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsCommon.Get, "InfisicalTag")]
[Cmdlet(VerbsCommon.Get, "InfisicalTag", DefaultParameterSetName = "List")]
[OutputType(typeof(InfisicalTag))]
public sealed class GetInfisicalTagCmdlet : InfisicalCmdletBase
{
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)]
[Parameter(ParameterSetName = "Single", Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)]
[Alias("Slug", "Id")]
public string TagSlugOrId { get; set; }
[Parameter] public string ProjectId { get; set; }
[Parameter(Mandatory = true)] public string ProjectId { get; set; }
[Parameter(ParameterSetName = "List")] public SwitchParameter List { get; set; }
protected override void ProcessRecord()
{
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
InfisicalTagClient client = new InfisicalTagClient(HttpClient, Logger);
InfisicalTag tag = client.Retrieve(connection, resolvedProjectId, TagSlugOrId);
if (string.Equals(ParameterSetName, "Single", StringComparison.Ordinal))
{
InfisicalTag tag = client.Retrieve(connection, ProjectId, TagSlugOrId);
if (tag != null)
{
WriteObject(tag);
}
return;
}
InfisicalTag[] tags = client.List(connection, ProjectId);
foreach (InfisicalTag tag in tags)
{
WriteObject(tag);
}
}
catch (Exception exception)
{
ThrowTerminatingForException("GetInfisicalTagCmdlet", "RetrieveTag", exception);
ThrowTerminatingForException("GetInfisicalTagCmdlet", "GetTag", exception);
}
}
}
@@ -1,34 +0,0 @@
using System;
using System.Management.Automation;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Models;
using PSInfisicalAPI.Tags;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsCommon.Get, "InfisicalTags")]
[OutputType(typeof(InfisicalTag))]
public sealed class GetInfisicalTagsCmdlet : InfisicalCmdletBase
{
[Parameter] public string ProjectId { get; set; }
protected override void ProcessRecord()
{
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
InfisicalTagClient client = new InfisicalTagClient(HttpClient, Logger);
InfisicalTag[] tags = client.List(connection, resolvedProjectId);
foreach (InfisicalTag tag in tags)
{
WriteObject(tag);
}
}
catch (Exception exception)
{
ThrowTerminatingForException("GetInfisicalTagsCmdlet", "ListTags", exception);
}
}
}
}
@@ -46,21 +46,6 @@ namespace PSInfisicalAPI.Cmdlets
ThrowTerminatingError(record);
}
protected string ResolveProjectId(InfisicalConnection connection, string explicitValue)
{
return ResolveValue("ProjectId", explicitValue, connection != null ? connection.ProjectId : null, null);
}
protected string ResolveEnvironment(InfisicalConnection connection, string explicitValue)
{
return ResolveValue("Environment", explicitValue, connection != null ? connection.Environment : null, null);
}
protected string ResolveSecretPath(InfisicalConnection connection, string explicitValue)
{
return ResolveValue("SecretPath", explicitValue, connection != null ? connection.DefaultSecretPath : null, "/");
}
protected string ResolveApiVersion(InfisicalConnection connection, string explicitValue)
{
string fromConnection = connection != null ? (!string.IsNullOrEmpty(connection.PinnedApiVersion) ? connection.PinnedApiVersion : connection.ApiVersion) : null;
@@ -44,7 +44,8 @@ namespace PSInfisicalAPI.Cmdlets
{
foreach (X509Certificate2 chainCert in ResolveChain())
{
InstallCertificate(chainCert, StoreName.CertificateAuthority, StoreLocation);
StoreName chainStore = InfisicalCertificateRequestHelpers.GetChainCertificateTargetStore(chainCert);
InstallCertificate(chainCert, chainStore, StoreLocation);
}
}
@@ -12,7 +12,7 @@ namespace PSInfisicalAPI.Cmdlets
{
[Parameter(Mandatory = true, Position = 0)] public string Name { get; set; }
[Parameter(Mandatory = true, Position = 1)] public string Slug { get; set; }
[Parameter] public string ProjectId { get; set; }
[Parameter(Mandatory = true)] public string ProjectId { get; set; }
[Parameter] public int? Position { get; set; }
protected override void ProcessRecord()
@@ -25,9 +25,8 @@ namespace PSInfisicalAPI.Cmdlets
}
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
InfisicalEnvironmentClient client = new InfisicalEnvironmentClient(HttpClient, Logger);
InfisicalEnvironment env = client.Create(connection, resolvedProjectId, Name, Slug, Position);
InfisicalEnvironment env = client.Create(connection, ProjectId, Name, Slug, Position);
if (env != null)
{
WriteObject(env);
@@ -11,8 +11,8 @@ namespace PSInfisicalAPI.Cmdlets
public sealed class NewInfisicalFolderCmdlet : InfisicalCmdletBase
{
[Parameter(Mandatory = true, Position = 0)] public string Name { get; set; }
[Parameter] public string ProjectId { get; set; }
[Parameter] public string Environment { get; set; }
[Parameter(Mandatory = true)] public string ProjectId { get; set; }
[Parameter(Mandatory = true)] public string Environment { get; set; }
[Parameter] public string Path { get; set; }
protected override void ProcessRecord()
@@ -25,11 +25,8 @@ namespace PSInfisicalAPI.Cmdlets
}
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
string resolvedEnvironment = ResolveEnvironment(connection, Environment);
string resolvedPath = ResolveSecretPath(connection, Path);
InfisicalFolderClient client = new InfisicalFolderClient(HttpClient, Logger);
InfisicalFolder folder = client.Create(connection, resolvedProjectId, resolvedEnvironment, Name, resolvedPath);
InfisicalFolder folder = client.Create(connection, ProjectId, Environment, Name, Path);
if (folder != null)
{
WriteObject(folder);
@@ -0,0 +1,49 @@
using System;
using System.Management.Automation;
using System.Security;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Pki;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsCommon.New, "InfisicalScepDynamicChallenge")]
[OutputType(typeof(SecureString))]
[OutputType(typeof(string))]
public sealed class NewInfisicalScepDynamicChallengeCmdlet : InfisicalCmdletBase
{
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)]
[Alias("Id")]
public string ApplicationId { get; set; }
[Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true)]
[Alias("CertificateProfileId")]
public string ProfileId { get; set; }
[Parameter] public SwitchParameter AsPlainText { get; set; }
protected override void ProcessRecord()
{
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
InfisicalPkiClient client = new InfisicalPkiClient(HttpClient, Logger);
string challenge = client.GenerateScepDynamicChallenge(connection, ApplicationId, ProfileId);
if (AsPlainText.IsPresent)
{
WriteObject(challenge);
return;
}
SecureString secure = new SecureString();
foreach (char c in challenge) { secure.AppendChar(c); }
secure.MakeReadOnly();
WriteObject(secure);
}
catch (Exception exception)
{
ThrowTerminatingForException("NewInfisicalScepDynamicChallengeCmdlet", "GenerateScepDynamicChallenge", exception);
}
}
}
}
@@ -28,8 +28,8 @@ namespace PSInfisicalAPI.Cmdlets
public IDictionary<string, string>[] Secrets { get; set; }
[Parameter] public string SecretComment { get; set; }
[Parameter] public string ProjectId { get; set; }
[Parameter] public string Environment { get; set; }
[Parameter(Mandatory = true)] public string ProjectId { get; set; }
[Parameter(Mandatory = true)] public string Environment { get; set; }
[Parameter] public string SecretPath { get; set; }
[Parameter] public string ApiVersion { get; set; }
[Parameter] public InfisicalSecretType Type { get; set; } = InfisicalSecretType.Shared;
@@ -41,9 +41,6 @@ namespace PSInfisicalAPI.Cmdlets
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
string resolvedEnvironment = ResolveEnvironment(connection, Environment);
string resolvedSecretPath = ResolveSecretPath(connection, SecretPath);
string resolvedApiVersion = ResolveApiVersion(connection, ApiVersion);
if (string.Equals(ParameterSetName, "Bulk", StringComparison.Ordinal))
@@ -54,9 +51,9 @@ namespace PSInfisicalAPI.Cmdlets
InfisicalBulkCreateSecretsRequest bulk = new InfisicalBulkCreateSecretsRequest
{
ProjectId = resolvedProjectId,
Environment = resolvedEnvironment,
SecretPath = resolvedSecretPath,
ProjectId = ProjectId,
Environment = Environment,
SecretPath = SecretPath,
ApiVersion = resolvedApiVersion,
Secrets = InfisicalBulkSecretConverter.ToCreateItems(Secrets)
};
@@ -82,9 +79,9 @@ namespace PSInfisicalAPI.Cmdlets
SecretName = SecretName,
SecretValue = plainValue,
SecretComment = SecretComment,
ProjectId = resolvedProjectId,
Environment = resolvedEnvironment,
SecretPath = resolvedSecretPath,
ProjectId = ProjectId,
Environment = Environment,
SecretPath = SecretPath,
Type = Type.ToString(),
ApiVersion = resolvedApiVersion,
SkipMultilineEncoding = SkipMultilineEncoding.IsPresent ? (bool?)true : null,
@@ -13,7 +13,7 @@ namespace PSInfisicalAPI.Cmdlets
[Parameter(Mandatory = true, Position = 0)] public string Slug { get; set; }
[Parameter] public string Name { get; set; }
[Parameter] public string Color { get; set; }
[Parameter] public string ProjectId { get; set; }
[Parameter(Mandatory = true)] public string ProjectId { get; set; }
protected override void ProcessRecord()
{
@@ -25,9 +25,8 @@ namespace PSInfisicalAPI.Cmdlets
}
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
InfisicalTagClient client = new InfisicalTagClient(HttpClient, Logger);
InfisicalTag tag = client.Create(connection, resolvedProjectId, Slug, Name, Color);
InfisicalTag tag = client.Create(connection, ProjectId, Slug, Name, Color);
if (tag != null)
{
WriteObject(tag);
@@ -12,7 +12,7 @@ namespace PSInfisicalAPI.Cmdlets
[Alias("Id")]
public string EnvironmentId { get; set; }
[Parameter] public string ProjectId { get; set; }
[Parameter(Mandatory = true)] public string ProjectId { get; set; }
[Parameter] public SwitchParameter PassThru { get; set; }
protected override void ProcessRecord()
@@ -25,9 +25,8 @@ namespace PSInfisicalAPI.Cmdlets
}
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
InfisicalEnvironmentClient client = new InfisicalEnvironmentClient(HttpClient, Logger);
client.Delete(connection, resolvedProjectId, EnvironmentId);
client.Delete(connection, ProjectId, EnvironmentId);
if (PassThru.IsPresent)
{
@@ -12,8 +12,8 @@ namespace PSInfisicalAPI.Cmdlets
[Alias("Id")]
public string FolderId { get; set; }
[Parameter] public string ProjectId { get; set; }
[Parameter] public string Environment { get; set; }
[Parameter(Mandatory = true)] public string ProjectId { get; set; }
[Parameter(Mandatory = true)] public string Environment { get; set; }
[Parameter] public string Path { get; set; }
[Parameter] public SwitchParameter PassThru { get; set; }
@@ -27,11 +27,8 @@ namespace PSInfisicalAPI.Cmdlets
}
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
string resolvedEnvironment = ResolveEnvironment(connection, Environment);
string resolvedPath = ResolveSecretPath(connection, Path);
InfisicalFolderClient client = new InfisicalFolderClient(HttpClient, Logger);
client.Delete(connection, resolvedProjectId, resolvedEnvironment, FolderId, resolvedPath);
client.Delete(connection, ProjectId, Environment, FolderId, Path);
if (PassThru.IsPresent)
{
@@ -8,7 +8,7 @@ namespace PSInfisicalAPI.Cmdlets
[Cmdlet(VerbsCommon.Remove, "InfisicalProject", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
public sealed class RemoveInfisicalProjectCmdlet : InfisicalCmdletBase
{
[Parameter(ValueFromPipelineByPropertyName = true, Position = 0)]
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)]
[Alias("Id")]
public string ProjectId { get; set; }
@@ -19,19 +19,18 @@ namespace PSInfisicalAPI.Cmdlets
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
if (!ShouldProcess(resolvedProjectId, "Remove Infisical project"))
if (!ShouldProcess(ProjectId, "Remove Infisical project"))
{
return;
}
InfisicalProjectClient client = new InfisicalProjectClient(HttpClient, Logger);
client.Delete(connection, resolvedProjectId);
client.Delete(connection, ProjectId);
if (PassThru.IsPresent)
{
WriteObject(resolvedProjectId);
WriteObject(ProjectId);
}
}
catch (Exception exception)
@@ -16,8 +16,8 @@ namespace PSInfisicalAPI.Cmdlets
[Alias("Names", "SecretKeys")]
public string[] SecretNames { get; set; }
[Parameter] public string ProjectId { get; set; }
[Parameter] public string Environment { get; set; }
[Parameter(Mandatory = true)] public string ProjectId { get; set; }
[Parameter(Mandatory = true)] public string Environment { get; set; }
[Parameter] public string SecretPath { get; set; }
[Parameter] public string ApiVersion { get; set; }
[Parameter] public InfisicalSecretType Type { get; set; } = InfisicalSecretType.Shared;
@@ -28,9 +28,6 @@ namespace PSInfisicalAPI.Cmdlets
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
string resolvedEnvironment = ResolveEnvironment(connection, Environment);
string resolvedSecretPath = ResolveSecretPath(connection, SecretPath);
string resolvedApiVersion = ResolveApiVersion(connection, ApiVersion);
InfisicalSecretsClient client = new InfisicalSecretsClient(HttpClient, Logger);
@@ -43,9 +40,9 @@ namespace PSInfisicalAPI.Cmdlets
InfisicalBulkDeleteSecretsRequest bulk = new InfisicalBulkDeleteSecretsRequest
{
ProjectId = resolvedProjectId,
Environment = resolvedEnvironment,
SecretPath = resolvedSecretPath,
ProjectId = ProjectId,
Environment = Environment,
SecretPath = SecretPath,
ApiVersion = resolvedApiVersion,
SecretNames = SecretNames
};
@@ -65,9 +62,9 @@ namespace PSInfisicalAPI.Cmdlets
InfisicalDeleteSecretRequest request = new InfisicalDeleteSecretRequest
{
SecretName = SecretName,
ProjectId = resolvedProjectId,
Environment = resolvedEnvironment,
SecretPath = resolvedSecretPath,
ProjectId = ProjectId,
Environment = Environment,
SecretPath = SecretPath,
Type = Type.ToString(),
ApiVersion = resolvedApiVersion
};
@@ -12,7 +12,7 @@ namespace PSInfisicalAPI.Cmdlets
[Alias("Id")]
public string TagId { get; set; }
[Parameter] public string ProjectId { get; set; }
[Parameter(Mandatory = true)] public string ProjectId { get; set; }
[Parameter] public SwitchParameter PassThru { get; set; }
protected override void ProcessRecord()
@@ -25,9 +25,8 @@ namespace PSInfisicalAPI.Cmdlets
}
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
InfisicalTagClient client = new InfisicalTagClient(HttpClient, Logger);
client.Delete(connection, resolvedProjectId, TagId);
client.Delete(connection, ProjectId, TagId);
if (PassThru.IsPresent)
{
@@ -0,0 +1,227 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Management.Automation;
using System.Security.Cryptography.X509Certificates;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Models;
using PSInfisicalAPI.Pki;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsLifecycle.Request, "InfisicalCertificate", SupportsShouldProcess = true, DefaultParameterSetName = "BySubscriber")]
[OutputType(typeof(InfisicalCertificateResult))]
public sealed class RequestInfisicalCertificateCmdlet : InfisicalCmdletBase
{
private const string Component = "RequestInfisicalCertificateCmdlet";
[Parameter(ParameterSetName = "BySubscriber", Mandatory = true, Position = 0)]
[Alias("Subscriber")]
public string PkiSubscriberSlug { get; set; }
[Parameter(ParameterSetName = "ByCa", Mandatory = true, Position = 0)]
[Alias("CaId")]
public string CertificateAuthorityId { get; set; }
[Parameter(ParameterSetName = "ByProfile", Mandatory = true, Position = 0)]
[Alias("ProfileId")]
public string CertificateProfileId { get; set; }
[Parameter(Mandatory = true)] public string ProjectId { get; set; }
[Parameter] public IDictionary Subject { get; set; }
[Parameter] public string CommonName { get; set; }
[Parameter] public string Country { get; set; }
[Parameter] public string State { get; set; }
[Parameter] public string Locality { get; set; }
[Parameter] public string Organization { get; set; }
[Parameter] public string OrganizationalUnit { get; set; }
[Parameter] public string EmailAddress { get; set; }
[Parameter] public string[] DnsName { get; set; }
[Parameter] public string[] IpAddress { get; set; }
[Parameter] public InfisicalKeyAlgorithm KeyAlgorithm { get; set; } = InfisicalKeyAlgorithm.Rsa;
[Parameter] public int KeySize { get; set; } = 2048;
[Parameter] public InfisicalEcCurve Curve { get; set; } = InfisicalEcCurve.P256;
[Parameter(ParameterSetName = "ByCa")]
[Parameter(ParameterSetName = "ByProfile")] public string Ttl { get; set; }
[Parameter(ParameterSetName = "ByCa")]
[Parameter(ParameterSetName = "ByProfile")] public string NotBefore { get; set; }
[Parameter(ParameterSetName = "ByCa")]
[Parameter(ParameterSetName = "ByProfile")] public string NotAfter { get; set; }
[Parameter(ParameterSetName = "ByCa")] public string FriendlyName { get; set; }
[Parameter(ParameterSetName = "ByCa")] public string PkiCollectionId { get; set; }
[Parameter(ParameterSetName = "ByCa")]
[Parameter(ParameterSetName = "ByProfile")] public string[] KeyUsage { get; set; }
[Parameter(ParameterSetName = "ByCa")]
[Parameter(ParameterSetName = "ByProfile")] public string[] ExtendedKeyUsage { get; set; }
[Parameter] public SwitchParameter Install { get; set; }
[Parameter] public StoreName StoreName { get; set; } = StoreName.My;
[Parameter] public StoreLocation StoreLocation { get; set; } = StoreLocation.CurrentUser;
[Parameter] public X509KeyStorageFlags KeyStorageFlags { get; set; } = X509KeyStorageFlags.DefaultKeySet;
[Parameter] public SwitchParameter InstallChain { get; set; }
[Parameter] public InfisicalPrivateKeyProtection PrivateKeyProtection { get; set; } = InfisicalPrivateKeyProtection.LocalOnly;
[Parameter] public SwitchParameter PersistKey { get; set; }
[Parameter] public SwitchParameter MachineKey { get; set; }
[Parameter] public string PrivateKeyPath { get; set; }
[Parameter] public SwitchParameter AllowRenewal { get; set; }
[Parameter] public int RenewalThresholdDays { get; set; } = 30;
[Parameter] public SwitchParameter Force { get; set; }
[Parameter] public SwitchParameter LocalChainOnly { get; set; }
protected override void ProcessRecord()
{
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
InfisicalPkiClient client = new InfisicalPkiClient(HttpClient, Logger);
InfisicalCsrSubject csrSubject = InfisicalCertificateRequestHelpers.MergeSubject(Subject, CommonName, Country, State, Locality, Organization, OrganizationalUnit, EmailAddress);
List<string> dnsNames = BuildDnsNames(csrSubject);
if (string.IsNullOrEmpty(csrSubject.CommonName) && dnsNames.Count > 0) { csrSubject.CommonName = dnsNames[0]; }
if (string.IsNullOrEmpty(csrSubject.CommonName)) { throw new InvalidOperationException("Subject CommonName could not be determined and no DnsName was provided."); }
X509Certificate2 existing = TryFindExisting(client, connection, ProjectId, csrSubject.CommonName);
if (existing != null && !Force.IsPresent && !(AllowRenewal.IsPresent && InfisicalLocalCertificateLookup.IsRenewable(existing, RenewalThresholdDays)))
{
Logger.Information(Component, string.Concat("Reusing existing certificate (Thumbprint=", existing.Thumbprint, ", NotAfter=", existing.NotAfter.ToString("u"), ")."));
InfisicalCertificateResult reuseResult = InfisicalCertificateRequestHelpers.BuildResultFromExistingLocal(existing);
if (!LocalChainOnly.IsPresent
&& (reuseResult.Root == null || reuseResult.Intermediates == null || reuseResult.Intermediates.Length == 0)
&& !string.IsNullOrEmpty(existing.SerialNumber))
{
try
{
InfisicalCertificateBundle bundle = client.GetCertificateBundle(connection, existing.SerialNumber);
if (bundle != null && !string.IsNullOrEmpty(bundle.CertificateChainPem))
{
reuseResult = InfisicalCertificateRequestHelpers.BuildResultFromExistingLocal(existing, bundle);
Logger.Information(Component, "Reused certificate chain completed from Infisical bundle.");
}
}
catch (Exception bundleException)
{
Logger.Verbose(Component, string.Concat("Infisical bundle fetch for reuse path failed (continuing with local-only chain): ", bundleException.Message));
}
}
WriteObject(reuseResult);
return;
}
string target = string.Concat("PKI subscriber '", PkiSubscriberSlug ?? "(n/a)", "', CA '", CertificateAuthorityId ?? "(n/a)", "', or profile '", CertificateProfileId ?? "(n/a)", "' for CN=", csrSubject.CommonName);
if (!ShouldProcess(target, "Request new certificate")) { return; }
InfisicalCsrOptions csrOptions = new InfisicalCsrOptions { KeyAlgorithm = KeyAlgorithm, RsaKeySize = KeySize, EcCurve = Curve };
InfisicalCsrResult csr = InfisicalCsrBuilder.Build(csrSubject, dnsNames, IpAddress, csrOptions);
InfisicalSignedCertificate signed = SignCertificate(client, connection, ProjectId, csr.CsrPem);
signed.PrivateKeyPem = csr.PrivateKeyPem;
if (string.IsNullOrEmpty(signed.CertificatePem))
{
Logger.Warning(Component, string.Concat("Issuance returned without a certificate (status='", signed.Status ?? "unknown", "'", string.IsNullOrEmpty(signed.StatusMessage) ? "" : string.Concat(", message='", signed.StatusMessage, "'"), string.IsNullOrEmpty(signed.CertificateRequestId) ? "" : string.Concat(", certificateRequestId='", signed.CertificateRequestId, "'"), "). Install / chain / key-write steps are skipped; emitting status-only result."));
InfisicalCertificateResult pending = InfisicalCertificateRequestHelpers.BuildResult(null, signed);
pending.PrivateKeyPem = null;
WriteObject(pending);
return;
}
X509KeyStorageFlags resolvedFlags = ResolveEffectiveKeyStorageFlags();
X509Certificate2 cert = PemCertificateBuilder.Build(signed.CertificatePem, signed.PrivateKeyPem, signed.CertificateChainPem, resolvedFlags);
if (Install.IsPresent)
{
InfisicalCertificateRequestHelpers.InstallToStore(cert, StoreName, StoreLocation, Force.IsPresent, Logger, Component);
if (InstallChain.IsPresent)
{
InfisicalCertificateRequestHelpers.InstallChain(signed, StoreLocation, Force.IsPresent, Logger, Component);
}
}
InfisicalCertificateResult resultObj = InfisicalCertificateRequestHelpers.BuildResult(cert, signed);
bool hasExplicitPath = !string.IsNullOrEmpty(PrivateKeyPath);
if (hasExplicitPath && !string.IsNullOrEmpty(resultObj.PrivateKeyPem))
{
InfisicalCertificateRequestHelpers.WritePrivateKeyPem(resultObj.PrivateKeyPem, PrivateKeyPath);
Logger.Information(Component, string.Concat("Wrote private key PEM to '", PrivateKeyPath, "'."));
}
if (!MyInvocation.BoundParameters.ContainsKey("KeyStorageFlags")
&& InfisicalCertificateRequestHelpers.ShouldScrubPrivateKeyPem(PrivateKeyProtection, hasExplicitPath))
{
resultObj.PrivateKeyPem = null;
}
WriteObject(resultObj);
}
catch (Exception exception)
{
ThrowTerminatingForException(Component, "RequestCertificate", exception);
}
}
private List<string> BuildDnsNames(InfisicalCsrSubject subject)
{
List<string> result = new List<string>();
if (DnsName != null) { foreach (string dns in DnsName) { if (!string.IsNullOrEmpty(dns)) { result.Add(dns); } } }
if (result.Count == 0)
{
string fqdn = InfisicalCertificateRequestHelpers.ResolveLocalFqdn();
if (!string.IsNullOrEmpty(fqdn)) { result.Add(fqdn); }
}
if (!string.IsNullOrEmpty(subject.CommonName) && !result.Contains(subject.CommonName)) { result.Insert(0, subject.CommonName); }
return result;
}
private X509Certificate2 TryFindExisting(InfisicalPkiClient client, InfisicalConnection connection, string projectId, string commonName)
{
List<string> candidateSerials = new List<string>();
try
{
InfisicalCertificateSearchQuery query = new InfisicalCertificateSearchQuery { ProjectId = projectId, CommonName = commonName, Status = "active", Limit = 50 };
InfisicalCertificateSearchResult page = client.SearchCertificates(connection, query);
if (page != null && page.Certificates != null)
{
foreach (InfisicalCertificate hit in page.Certificates) { if (!string.IsNullOrEmpty(hit.SerialNumber)) { candidateSerials.Add(hit.SerialNumber); } }
}
}
catch (Exception searchException)
{
Logger.Verbose(Component, string.Concat("Infisical search for idempotency check failed: ", searchException.Message));
}
return InfisicalLocalCertificateLookup.FindMatch(StoreName, StoreLocation, commonName, candidateSerials);
}
private X509KeyStorageFlags ResolveEffectiveKeyStorageFlags()
{
if (MyInvocation.BoundParameters.ContainsKey("KeyStorageFlags"))
{
return KeyStorageFlags;
}
return InfisicalCertificateRequestHelpers.ResolveKeyStorageFlags(PrivateKeyProtection, PersistKey.IsPresent, MachineKey.IsPresent);
}
private InfisicalSignedCertificate SignCertificate(InfisicalPkiClient client, InfisicalConnection connection, string projectId, string csrPem)
{
if (string.Equals(ParameterSetName, "BySubscriber", StringComparison.Ordinal))
{
return client.SignCertificateBySubscriber(connection, PkiSubscriberSlug, projectId, csrPem);
}
if (string.Equals(ParameterSetName, "ByProfile", StringComparison.Ordinal))
{
InfisicalCsrSubject subject = InfisicalCertificateRequestHelpers.MergeSubject(Subject, CommonName, Country, State, Locality, Organization, OrganizationalUnit, EmailAddress);
return client.IssueCertificateByProfile(connection, CertificateProfileId, csrPem, subject.CommonName, subject.Organization, subject.OrganizationalUnit, subject.Country, subject.State, subject.Locality, Ttl, NotBefore, NotAfter, KeyUsage, ExtendedKeyUsage);
}
return client.SignCertificateByCa(connection, CertificateAuthorityId, csrPem, CommonName, null, Ttl, NotBefore, NotAfter, FriendlyName, PkiCollectionId, KeyUsage, ExtendedKeyUsage);
}
}
}
@@ -11,7 +11,7 @@ namespace PSInfisicalAPI.Cmdlets
[OutputType(typeof(InfisicalCertificate))]
public sealed class SearchInfisicalCertificateCmdlet : InfisicalCmdletBase
{
[Parameter] public string ProjectId { get; set; }
[Parameter(Mandatory = true)] public string ProjectId { get; set; }
[Parameter] public string FriendlyName { get; set; }
[Parameter] public string CommonName { get; set; }
[Parameter] public string Search { get; set; }
@@ -39,10 +39,9 @@ namespace PSInfisicalAPI.Cmdlets
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
InfisicalPkiClient client = new InfisicalPkiClient(HttpClient, Logger);
InfisicalCertificateSearchQuery query = BuildQuery(resolvedProjectId);
InfisicalCertificateSearchQuery query = BuildQuery(ProjectId);
int requestedLimit = query.Limit ?? 100;
query.Limit = requestedLimit;
query.Offset = query.Offset ?? 0;
@@ -14,7 +14,7 @@ namespace PSInfisicalAPI.Cmdlets
[Alias("Id")]
public string EnvironmentId { get; set; }
[Parameter] public string ProjectId { get; set; }
[Parameter(Mandatory = true)] public string ProjectId { get; set; }
[Parameter] public string Name { get; set; }
[Parameter] public string Slug { get; set; }
[Parameter] public int? Position { get; set; }
@@ -29,9 +29,8 @@ namespace PSInfisicalAPI.Cmdlets
}
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
InfisicalEnvironmentClient client = new InfisicalEnvironmentClient(HttpClient, Logger);
InfisicalEnvironment env = client.Update(connection, resolvedProjectId, EnvironmentId, Name, Slug, Position);
InfisicalEnvironment env = client.Update(connection, ProjectId, EnvironmentId, Name, Slug, Position);
if (env != null)
{
WriteObject(env);
@@ -15,8 +15,8 @@ namespace PSInfisicalAPI.Cmdlets
public string FolderId { get; set; }
[Parameter(Mandatory = true, Position = 1)] public string Name { get; set; }
[Parameter] public string ProjectId { get; set; }
[Parameter] public string Environment { get; set; }
[Parameter(Mandatory = true)] public string ProjectId { get; set; }
[Parameter(Mandatory = true)] public string Environment { get; set; }
[Parameter] public string Path { get; set; }
protected override void ProcessRecord()
@@ -29,11 +29,8 @@ namespace PSInfisicalAPI.Cmdlets
}
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
string resolvedEnvironment = ResolveEnvironment(connection, Environment);
string resolvedPath = ResolveSecretPath(connection, Path);
InfisicalFolderClient client = new InfisicalFolderClient(HttpClient, Logger);
InfisicalFolder folder = client.Update(connection, resolvedProjectId, resolvedEnvironment, FolderId, Name, resolvedPath);
InfisicalFolder folder = client.Update(connection, ProjectId, Environment, FolderId, Name, Path);
if (folder != null)
{
WriteObject(folder);
@@ -10,7 +10,7 @@ namespace PSInfisicalAPI.Cmdlets
[OutputType(typeof(InfisicalProject))]
public sealed class UpdateInfisicalProjectCmdlet : InfisicalCmdletBase
{
[Parameter(ValueFromPipelineByPropertyName = true, Position = 0)]
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)]
[Alias("Id")]
public string ProjectId { get; set; }
@@ -23,15 +23,14 @@ namespace PSInfisicalAPI.Cmdlets
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
if (!ShouldProcess(resolvedProjectId, "Update Infisical project"))
if (!ShouldProcess(ProjectId, "Update Infisical project"))
{
return;
}
InfisicalProjectClient client = new InfisicalProjectClient(HttpClient, Logger);
InfisicalProject project = client.Update(connection, resolvedProjectId, Name, Description, AutoCapitalization);
InfisicalProject project = client.Update(connection, ProjectId, Name, Description, AutoCapitalization);
if (project != null)
{
WriteObject(project);
@@ -26,8 +26,8 @@ namespace PSInfisicalAPI.Cmdlets
[Parameter] public string NewSecretName { get; set; }
[Parameter] public string SecretComment { get; set; }
[Parameter] public string ProjectId { get; set; }
[Parameter] public string Environment { get; set; }
[Parameter(Mandatory = true)] public string ProjectId { get; set; }
[Parameter(Mandatory = true)] public string Environment { get; set; }
[Parameter] public string SecretPath { get; set; }
[Parameter] public string ApiVersion { get; set; }
[Parameter] public InfisicalSecretType Type { get; set; } = InfisicalSecretType.Shared;
@@ -39,9 +39,6 @@ namespace PSInfisicalAPI.Cmdlets
try
{
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
string resolvedEnvironment = ResolveEnvironment(connection, Environment);
string resolvedSecretPath = ResolveSecretPath(connection, SecretPath);
string resolvedApiVersion = ResolveApiVersion(connection, ApiVersion);
if (string.Equals(ParameterSetName, "Bulk", StringComparison.Ordinal))
@@ -52,9 +49,9 @@ namespace PSInfisicalAPI.Cmdlets
InfisicalBulkUpdateSecretsRequest bulk = new InfisicalBulkUpdateSecretsRequest
{
ProjectId = resolvedProjectId,
Environment = resolvedEnvironment,
SecretPath = resolvedSecretPath,
ProjectId = ProjectId,
Environment = Environment,
SecretPath = SecretPath,
ApiVersion = resolvedApiVersion,
Secrets = InfisicalBulkSecretConverter.ToUpdateItems(Secrets)
};
@@ -81,9 +78,9 @@ namespace PSInfisicalAPI.Cmdlets
NewSecretName = NewSecretName,
SecretValue = plainValue,
SecretComment = SecretComment,
ProjectId = resolvedProjectId,
Environment = resolvedEnvironment,
SecretPath = resolvedSecretPath,
ProjectId = ProjectId,
Environment = Environment,
SecretPath = SecretPath,
Type = Type.ToString(),
ApiVersion = resolvedApiVersion,
SkipMultilineEncoding = SkipMultilineEncoding.IsPresent ? (bool?)true : null,
@@ -17,7 +17,7 @@ namespace PSInfisicalAPI.Cmdlets
[Parameter] public string Slug { get; set; }
[Parameter] public string Name { get; set; }
[Parameter] public string Color { get; set; }
[Parameter] public string ProjectId { get; set; }
[Parameter(Mandatory = true)] public string ProjectId { get; set; }
protected override void ProcessRecord()
{
@@ -29,9 +29,8 @@ namespace PSInfisicalAPI.Cmdlets
}
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
InfisicalTagClient client = new InfisicalTagClient(HttpClient, Logger);
InfisicalTag tag = client.Update(connection, resolvedProjectId, TagId, Slug, Name, Color);
InfisicalTag tag = client.Update(connection, ProjectId, TagId, Slug, Name, Color);
if (tag != null)
{
WriteObject(tag);
@@ -0,0 +1,151 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Management.Automation;
using System.Text;
using PSInfisicalAPI.Models;
namespace PSInfisicalAPI.Cmdlets
{
[Cmdlet(VerbsCommunications.Write, "InfisicalScepMdmProfileToWmi", SupportsShouldProcess = true)]
[OutputType(typeof(PSObject))]
public sealed class WriteInfisicalScepMdmProfileToWmiCmdlet : InfisicalCmdletBase
{
private const string Component = "WriteInfisicalScepMdmProfileToWmiCmdlet";
[Parameter(Mandatory = true, ValueFromPipeline = true, Position = 0)]
[Alias("Profile", "ScepProfile")]
public InfisicalScepMdmProfile InputObject { get; set; }
[Parameter] public string Namespace { get; set; } = "root/cimv2/mdm/dmmap";
[Parameter] public string ClassName { get; set; } = "MDM_ClientCertificateInstall_SCEP02";
[Parameter] public SwitchParameter SkipElevationCheck { get; set; }
[Parameter] public SwitchParameter PassThru { get; set; }
protected override void ProcessRecord()
{
try
{
if (InputObject == null) { throw new InvalidOperationException("InputObject is required."); }
if (string.IsNullOrEmpty(InputObject.UniqueId)) { throw new InvalidOperationException("InputObject.UniqueId is required."); }
if (string.IsNullOrEmpty(InputObject.ServerUrl)) { throw new InvalidOperationException("InputObject.ServerUrl is required."); }
if (Environment.OSVersion.Platform != PlatformID.Win32NT)
{
throw new PlatformNotSupportedException("Write-InfisicalScepMdmProfileToWmi requires Windows (MDM Bridge WMI provider).");
}
bool deviceScope = !string.Equals(InputObject.Scope, "User", StringComparison.OrdinalIgnoreCase);
if (deviceScope && !SkipElevationCheck.IsPresent && !IsElevated())
{
throw new UnauthorizedAccessException("Device-scope SCEP enrollment requires an elevated session (run as Administrator or SYSTEM). Pass -SkipElevationCheck to bypass this guard.");
}
string parentId = string.Concat("./Vendor/MSFT/ClientCertificateInstall/SCEP/", InputObject.UniqueId);
Hashtable properties = BuildProperties(InputObject, parentId);
string target = string.Concat(Namespace, " ", ClassName, " ParentID=", parentId);
if (!ShouldProcess(target, "New-CimInstance MDM SCEP enrollment"))
{
return;
}
Logger.Verbose(Component, string.Concat("Creating CIM instance in namespace '", Namespace, "' for class '", ClassName, "' with ParentID '", parentId, "'."));
Collection<PSObject> results = InvokeNewCimInstance(Namespace, ClassName, properties);
Logger.Information(Component, string.Concat("Submitted SCEP MDM profile '", InputObject.UniqueId, "' to MDM Bridge WMI provider (results=", results != null ? results.Count : 0, ")."));
if (PassThru.IsPresent && results != null)
{
foreach (PSObject result in results) { WriteObject(result); }
}
}
catch (Exception exception)
{
ThrowTerminatingForException(Component, "WriteScepMdmProfileToWmi", exception);
}
}
private bool IsElevated()
{
try
{
Collection<PSObject> results = InvokeCommand.InvokeScript("[bool]([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)");
if (results == null || results.Count == 0 || results[0] == null || results[0].BaseObject == null) { return false; }
return Convert.ToBoolean(results[0].BaseObject, CultureInfo.InvariantCulture);
}
catch (Exception ex)
{
Logger.Verbose(Component, string.Concat("Elevation check failed; assuming non-elevated. ", ex.Message));
return false;
}
}
private Collection<PSObject> InvokeNewCimInstance(string ns, string className, Hashtable properties)
{
Dictionary<string, object> variables = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)
{
{ "ns", ns },
{ "class", className },
{ "props", properties }
};
foreach (KeyValuePair<string, object> kv in variables)
{
SessionState.PSVariable.Set(kv.Key, kv.Value);
}
try
{
return InvokeCommand.InvokeScript("New-CimInstance -Namespace $ns -ClassName $class -Property $props -ErrorAction Stop");
}
finally
{
foreach (KeyValuePair<string, object> kv in variables)
{
SessionState.PSVariable.Remove(kv.Key);
}
}
}
private static Hashtable BuildProperties(InfisicalScepMdmProfile profile, string parentId)
{
Hashtable h = new Hashtable(StringComparer.OrdinalIgnoreCase);
h["ParentID"] = parentId;
h["InstanceID"] = "Install";
AddString(h, "ServerURL", profile.ServerUrl);
AddString(h, "Challenge", profile.Challenge);
AddString(h, "SubjectName", profile.SubjectName);
AddString(h, "SubjectAlternativeNames", profile.SubjectAlternativeNames);
AddString(h, "EKUMapping", profile.EkuMapping);
AddInt(h, "KeyUsage", profile.KeyUsage);
AddInt(h, "KeyLength", profile.KeyLength);
AddString(h, "KeyAlgorithm", profile.KeyAlgorithm);
AddString(h, "HashAlgorithm", profile.HashAlgorithm);
AddInt(h, "KeyProtection", profile.KeyProtection);
AddString(h, "ContainerName", profile.ContainerName);
AddString(h, "ValidPeriod", profile.ValidPeriod);
AddInt(h, "ValidPeriodUnits", profile.ValidPeriodUnits);
AddInt(h, "RetryCount", profile.RetryCount);
AddInt(h, "RetryDelay", profile.RetryDelay);
AddString(h, "TemplateName", profile.TemplateName);
AddString(h, "CAThumbprint", profile.CAThumbprint);
AddString(h, "CustomTextToShowInPrompt", profile.CustomTextToShowInPrompt);
return h;
}
private static void AddString(Hashtable h, string key, string value)
{
if (string.IsNullOrEmpty(value)) { return; }
h[key] = value;
}
private static void AddInt(Hashtable h, string key, int? value)
{
if (!value.HasValue) { return; }
h[key] = value.Value;
}
}
}
@@ -12,9 +12,6 @@ namespace PSInfisicalAPI.Connections
public string PinnedApiVersion { get; set; }
public InfisicalAuthType AuthType { get; set; }
public string OrganizationId { get; set; }
public string ProjectId { get; set; }
public string Environment { get; set; }
public string DefaultSecretPath { get; set; }
public DateTimeOffset ConnectedAtUtc { get; set; }
public DateTimeOffset? ExpiresAtUtc { get; set; }
public bool IsConnected { get; set; }
@@ -26,8 +23,8 @@ namespace PSInfisicalAPI.Connections
public override string ToString()
{
return string.Concat(
"Project=", ProjectId ?? "",
" Environment=", Environment ?? "",
"BaseUri=", BaseUri != null ? BaseUri.ToString() : "",
" AuthType=", AuthType.ToString(),
" Connected=", IsConnected ? "true" : "false");
}
}
@@ -49,5 +49,27 @@ namespace PSInfisicalAPI.Endpoints
public const string SearchCertificates = "SearchCertificates";
public const string RetrieveCertificate = "RetrieveCertificate";
public const string GetCertificateBundle = "GetCertificateBundle";
public const string SignCertificateBySubscriber = "SignCertificateBySubscriber";
public const string SignCertificateByCa = "SignCertificateByCa";
public const string IssueCertificateByProfile = "IssueCertificateByProfile";
public const string ListPkiSubscribers = "ListPkiSubscribers";
public const string GetPkiSubscriber = "GetPkiSubscriber";
public const string ListCertificateProfiles = "ListCertificateProfiles";
public const string GetCertificateProfile = "GetCertificateProfile";
public const string ListCertificatePolicies = "ListCertificatePolicies";
public const string GetCertificatePolicy = "GetCertificatePolicy";
public const string ListCertificateAuthorities = "ListCertificateAuthorities";
public const string ListCertificateApplications = "ListCertificateApplications";
public const string GetCertificateApplication = "GetCertificateApplication";
public const string GetCertificateApplicationByName = "GetCertificateApplicationByName";
public const string ListCertificateApplicationProfiles = "ListCertificateApplicationProfiles";
public const string GetCertificateApplicationEnrollment = "GetCertificateApplicationEnrollment";
public const string GenerateScepDynamicChallenge = "GenerateScepDynamicChallenge";
}
}
@@ -589,6 +589,181 @@ namespace PSInfisicalAPI.Endpoints
RequiresAuthorization = true,
ContainsSecretMaterialInResponse = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.SignCertificateBySubscriber,
Resource = "Pki",
Version = "v1",
Method = "POST",
Template = "/api/v1/pki/subscribers/{subscriberName}/sign-certificate",
RequiresAuthorization = true,
ContainsSecretMaterialInResponse = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.SignCertificateByCa,
Resource = "Pki",
Version = "v1",
Method = "POST",
Template = "/api/v1/pki/ca/{caId}/sign-certificate",
RequiresAuthorization = true,
ContainsSecretMaterialInResponse = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.SignCertificateByCa,
Resource = "Pki",
Version = "v1",
Method = "POST",
Template = "/api/v1/cert-manager/ca/{caId}/sign-certificate",
RequiresAuthorization = true,
ContainsSecretMaterialInResponse = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.IssueCertificateByProfile,
Resource = "Pki",
Version = "v1",
Method = "POST",
Template = "/api/v1/cert-manager/certificates",
RequiresAuthorization = true,
ContainsSecretMaterialInResponse = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.ListPkiSubscribers,
Resource = "Pki",
Version = "v1",
Method = "GET",
Template = "/api/v1/projects/{projectId}/pki-subscribers",
RequiresAuthorization = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.GetPkiSubscriber,
Resource = "Pki",
Version = "v1",
Method = "GET",
Template = "/api/v1/pki/subscribers/{subscriberName}",
RequiresAuthorization = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.ListCertificateProfiles,
Resource = "Pki",
Version = "v1",
Method = "GET",
Template = "/api/v1/cert-manager/certificate-profiles",
RequiresAuthorization = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.GetCertificateProfile,
Resource = "Pki",
Version = "v1",
Method = "GET",
Template = "/api/v1/cert-manager/certificate-profiles/{certificateProfileId}",
RequiresAuthorization = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.ListCertificatePolicies,
Resource = "Pki",
Version = "v1",
Method = "GET",
Template = "/api/v1/cert-manager/certificate-policies",
RequiresAuthorization = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.GetCertificatePolicy,
Resource = "Pki",
Version = "v1",
Method = "GET",
Template = "/api/v1/cert-manager/certificate-policies/{certificatePolicyId}",
RequiresAuthorization = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.ListCertificateAuthorities,
Resource = "Pki",
Version = "v1",
Method = "GET",
Template = "/api/v1/cert-manager/ca",
RequiresAuthorization = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.ListCertificateApplications,
Resource = "Pki",
Version = "v1",
Method = "GET",
Template = "/api/v1/cert-manager/applications",
RequiresAuthorization = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.GetCertificateApplication,
Resource = "Pki",
Version = "v1",
Method = "GET",
Template = "/api/v1/cert-manager/applications/{applicationId}",
RequiresAuthorization = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.GetCertificateApplicationByName,
Resource = "Pki",
Version = "v1",
Method = "GET",
Template = "/api/v1/cert-manager/applications/by-name/{name}",
RequiresAuthorization = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.ListCertificateApplicationProfiles,
Resource = "Pki",
Version = "v1",
Method = "GET",
Template = "/api/v1/cert-manager/applications/{applicationId}/profiles",
RequiresAuthorization = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.GetCertificateApplicationEnrollment,
Resource = "Pki",
Version = "v1",
Method = "GET",
Template = "/api/v1/cert-manager/applications/{applicationId}/profiles/{profileId}/enrollment",
RequiresAuthorization = true
});
Add(map, new InfisicalEndpointDefinition
{
Name = InfisicalEndpointNames.GenerateScepDynamicChallenge,
Resource = "Pki",
Version = "v1",
Method = "POST",
Template = "/scep/applications/{applicationId}/profiles/{profileId}/challenge",
RequiresAuthorization = true,
ContainsSecretMaterialInResponse = true
});
}
public static InfisicalEndpointDefinition Get(string name)
@@ -29,10 +29,9 @@ namespace PSInfisicalAPI.Environments
public InfisicalEnvironment[] List(InfisicalConnection connection, string projectId)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(projectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", resolvedProjectId } };
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", projectId } };
try
{
@@ -43,7 +42,7 @@ namespace PSInfisicalAPI.Environments
InfisicalEnvironmentWorkspaceDto workspace = dto != null ? (dto.Workspace ?? dto.Project) : null;
List<InfisicalEnvironmentResponseDto> envs = workspace != null ? workspace.Environments : null;
InfisicalEnvironment[] mapped = InfisicalEnvironmentMapper.MapMany(envs, resolvedProjectId);
InfisicalEnvironment[] mapped = InfisicalEnvironmentMapper.MapMany(envs, projectId);
_logger.Information(Component, "Infisical environment list retrieval was successful.");
return mapped;
}
@@ -57,11 +56,10 @@ namespace PSInfisicalAPI.Environments
public InfisicalEnvironment Retrieve(InfisicalConnection connection, string projectId, string environmentSlugOrId)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(projectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(environmentSlugOrId)) { throw new InfisicalConfigurationException("Environment is required."); }
InfisicalEnvironment[] all = List(connection, resolvedProjectId);
InfisicalEnvironment[] all = List(connection, projectId);
foreach (InfisicalEnvironment env in all)
{
if (string.Equals(env.Id, environmentSlugOrId, StringComparison.OrdinalIgnoreCase) ||
@@ -77,12 +75,11 @@ namespace PSInfisicalAPI.Environments
public InfisicalEnvironment Create(InfisicalConnection connection, string projectId, string name, string slug, int? position)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(projectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(name)) { throw new InfisicalConfigurationException("Name is required."); }
if (string.IsNullOrEmpty(slug)) { throw new InfisicalConfigurationException("Slug is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", resolvedProjectId } };
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", projectId } };
InfisicalEnvironmentCreateRequestDto request = new InfisicalEnvironmentCreateRequestDto { Name = name, Slug = slug, Position = position };
string body = _serializer.Serialize(request);
@@ -93,7 +90,7 @@ namespace PSInfisicalAPI.Environments
InfisicalEnvironmentSingleResponseDto dto = _serializer.Deserialize<InfisicalEnvironmentSingleResponseDto>(response.Body);
response.Clear();
InfisicalEnvironment mapped = InfisicalEnvironmentMapper.Map(dto != null ? dto.Environment : null, resolvedProjectId);
InfisicalEnvironment mapped = InfisicalEnvironmentMapper.Map(dto != null ? dto.Environment : null, projectId);
_logger.Information(Component, "Infisical environment creation was successful.");
return mapped;
}
@@ -107,11 +104,10 @@ namespace PSInfisicalAPI.Environments
public InfisicalEnvironment Update(InfisicalConnection connection, string projectId, string environmentId, string name, string slug, int? position)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(projectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(environmentId)) { throw new InfisicalConfigurationException("EnvironmentId is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", resolvedProjectId }, { "environmentId", environmentId } };
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", projectId }, { "environmentId", environmentId } };
InfisicalEnvironmentUpdateRequestDto request = new InfisicalEnvironmentUpdateRequestDto { Name = name, Slug = slug, Position = position };
string body = _serializer.Serialize(request);
@@ -122,7 +118,7 @@ namespace PSInfisicalAPI.Environments
InfisicalEnvironmentSingleResponseDto dto = _serializer.Deserialize<InfisicalEnvironmentSingleResponseDto>(response.Body);
response.Clear();
InfisicalEnvironment mapped = InfisicalEnvironmentMapper.Map(dto != null ? dto.Environment : null, resolvedProjectId);
InfisicalEnvironment mapped = InfisicalEnvironmentMapper.Map(dto != null ? dto.Environment : null, projectId);
_logger.Information(Component, "Infisical environment update was successful.");
return mapped;
}
@@ -136,11 +132,10 @@ namespace PSInfisicalAPI.Environments
public void Delete(InfisicalConnection connection, string projectId, string environmentId)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(projectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(environmentId)) { throw new InfisicalConfigurationException("EnvironmentId is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", resolvedProjectId }, { "environmentId", environmentId } };
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", projectId }, { "environmentId", environmentId } };
try
{
@@ -156,11 +151,5 @@ namespace PSInfisicalAPI.Environments
}
}
private static string FirstNonEmpty(params string[] values)
{
if (values == null) { return null; }
foreach (string value in values) { if (!string.IsNullOrEmpty(value)) { return value; } }
return null;
}
}
}
@@ -0,0 +1,119 @@
using System;
using Newtonsoft.Json.Linq;
namespace PSInfisicalAPI.Errors
{
internal static class InfisicalApiErrorEnvelope
{
public static void Enrich(InfisicalApiException exception, string body)
{
if (exception == null || string.IsNullOrEmpty(body))
{
return;
}
string trimmed = body.TrimStart();
if (trimmed.Length == 0 || (trimmed[0] != '{' && trimmed[0] != '['))
{
return;
}
JObject obj;
try
{
JToken token = JToken.Parse(body);
if (token.Type != JTokenType.Object) { return; }
obj = (JObject)token;
}
catch (Exception)
{
return;
}
string message = ReadString(obj, "message");
string error = ReadString(obj, "error");
string reqId = ReadString(obj, "reqId");
if (!string.IsNullOrEmpty(message)) { exception.ApiErrorMessage = message; }
if (!string.IsNullOrEmpty(error) && string.IsNullOrEmpty(exception.ApiErrorCode)) { exception.ApiErrorCode = error; }
if (!string.IsNullOrEmpty(reqId)) { exception.ApiRequestId = reqId; }
}
public static string BuildExceptionMessage(int statusCode, string reasonPhrase, string body)
{
string baseMessage = string.Concat(
"Infisical API returned ",
statusCode.ToString(System.Globalization.CultureInfo.InvariantCulture),
" (", reasonPhrase ?? string.Empty, ").");
string apiMessage = null;
string apiError = null;
string reqId = null;
if (!string.IsNullOrEmpty(body))
{
string trimmed = body.TrimStart();
if (trimmed.Length > 0 && trimmed[0] == '{')
{
try
{
JToken token = JToken.Parse(body);
if (token.Type == JTokenType.Object)
{
JObject obj = (JObject)token;
apiMessage = ReadString(obj, "message");
apiError = ReadString(obj, "error");
reqId = ReadString(obj, "reqId");
}
}
catch (Exception)
{
}
}
}
if (string.IsNullOrEmpty(apiMessage) && string.IsNullOrEmpty(apiError) && string.IsNullOrEmpty(reqId))
{
return baseMessage;
}
System.Text.StringBuilder builder = new System.Text.StringBuilder(baseMessage);
if (!string.IsNullOrEmpty(apiMessage))
{
builder.Append(' ').Append(apiMessage);
}
if (!string.IsNullOrEmpty(apiError) || !string.IsNullOrEmpty(reqId))
{
builder.Append(" [");
bool needsSeparator = false;
if (!string.IsNullOrEmpty(apiError))
{
builder.Append("error=").Append(apiError);
needsSeparator = true;
}
if (!string.IsNullOrEmpty(reqId))
{
if (needsSeparator) { builder.Append("; "); }
builder.Append("reqId=").Append(reqId);
}
builder.Append(']');
}
return builder.ToString();
}
private static string ReadString(JObject obj, string name)
{
JToken token;
if (obj.TryGetValue(name, StringComparison.OrdinalIgnoreCase, out token) && token != null && token.Type == JTokenType.String)
{
return (string)token;
}
return null;
}
}
}
@@ -10,6 +10,8 @@ namespace PSInfisicalAPI.Errors
public int? StatusCode { get; set; }
public string ReasonPhrase { get; set; }
public string ApiErrorCode { get; set; }
public string ApiErrorMessage { get; set; }
public string ApiRequestId { get; set; }
public string SanitizedBody { get; set; }
public int? LineNumber { get; set; }
public int? LinePosition { get; set; }
@@ -26,6 +26,8 @@ namespace PSInfisicalAPI.Errors
details.StatusCode = apiException.StatusCode;
details.ReasonPhrase = apiException.ReasonPhrase;
details.ApiErrorCode = apiException.ApiErrorCode;
details.ApiErrorMessage = apiException.ApiErrorMessage;
details.ApiRequestId = apiException.ApiRequestId;
details.SanitizedBody = apiException.SanitizedBody;
details.EndpointName = apiException.EndpointName;
details.RequestMethod = apiException.RequestMethod;
@@ -70,6 +72,16 @@ namespace PSInfisicalAPI.Errors
logger.Error(Component, string.Concat("API Error Code: ", details.ApiErrorCode));
}
if (!string.IsNullOrEmpty(details.ApiErrorMessage))
{
logger.Error(Component, string.Concat("API Error Message: ", details.ApiErrorMessage));
}
if (!string.IsNullOrEmpty(details.ApiRequestId))
{
logger.Error(Component, string.Concat("API Request Id: ", details.ApiRequestId));
}
if (details.LineNumber.HasValue)
{
logger.Error(Component, string.Concat("Line: ", details.LineNumber.Value.ToString(CultureInfo.InvariantCulture)));
@@ -33,6 +33,8 @@ namespace PSInfisicalAPI.Errors
public int StatusCode { get; set; }
public string ReasonPhrase { get; set; }
public string ApiErrorCode { get; set; }
public string ApiErrorMessage { get; set; }
public string ApiRequestId { get; set; }
public string SanitizedBody { get; set; }
public string EndpointName { get; set; }
public string RequestMethod { get; set; }
@@ -29,16 +29,14 @@ namespace PSInfisicalAPI.Folders
public InfisicalFolder[] List(InfisicalConnection connection, string projectId, string environment, string path)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
string resolvedEnvironment = FirstNonEmpty(environment, connection.Environment);
string resolvedPath = FirstNonEmpty(path, connection.DefaultSecretPath, "/");
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(resolvedEnvironment)) { throw new InfisicalConfigurationException("Environment is required."); }
if (string.IsNullOrEmpty(projectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(environment)) { throw new InfisicalConfigurationException("Environment is required."); }
string resolvedPath = FirstNonEmpty(path, "/");
List<KeyValuePair<string, string>> queryParameters = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("workspaceId", resolvedProjectId),
new KeyValuePair<string, string>("environment", resolvedEnvironment),
new KeyValuePair<string, string>("workspaceId", projectId),
new KeyValuePair<string, string>("environment", environment),
new KeyValuePair<string, string>("path", resolvedPath)
};
@@ -49,7 +47,7 @@ namespace PSInfisicalAPI.Folders
InfisicalFolderListResponseDto dto = _serializer.Deserialize<InfisicalFolderListResponseDto>(response.Body);
response.Clear();
InfisicalFolder[] mapped = InfisicalFolderMapper.MapMany(dto != null ? dto.Folders : null, resolvedProjectId, resolvedEnvironment);
InfisicalFolder[] mapped = InfisicalFolderMapper.MapMany(dto != null ? dto.Folders : null, projectId, environment);
_logger.Information(Component, "Infisical folder list retrieval was successful.");
return mapped;
}
@@ -80,17 +78,15 @@ namespace PSInfisicalAPI.Folders
public InfisicalFolder Create(InfisicalConnection connection, string projectId, string environment, string name, string path)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
string resolvedEnvironment = FirstNonEmpty(environment, connection.Environment);
string resolvedPath = FirstNonEmpty(path, connection.DefaultSecretPath, "/");
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(resolvedEnvironment)) { throw new InfisicalConfigurationException("Environment is required."); }
if (string.IsNullOrEmpty(projectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(environment)) { throw new InfisicalConfigurationException("Environment is required."); }
if (string.IsNullOrEmpty(name)) { throw new InfisicalConfigurationException("Name is required."); }
string resolvedPath = FirstNonEmpty(path, "/");
InfisicalFolderCreateRequestDto request = new InfisicalFolderCreateRequestDto
{
WorkspaceId = resolvedProjectId,
Environment = resolvedEnvironment,
WorkspaceId = projectId,
Environment = environment,
Name = name,
Path = resolvedPath
};
@@ -103,7 +99,7 @@ namespace PSInfisicalAPI.Folders
InfisicalFolderSingleResponseDto dto = _serializer.Deserialize<InfisicalFolderSingleResponseDto>(response.Body);
response.Clear();
InfisicalFolder mapped = InfisicalFolderMapper.Map(dto != null ? dto.Folder : null, resolvedProjectId, resolvedEnvironment);
InfisicalFolder mapped = InfisicalFolderMapper.Map(dto != null ? dto.Folder : null, projectId, environment);
_logger.Information(Component, "Infisical folder creation was successful.");
return mapped;
}
@@ -117,19 +113,17 @@ namespace PSInfisicalAPI.Folders
public InfisicalFolder Update(InfisicalConnection connection, string projectId, string environment, string folderId, string name, string path)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
string resolvedEnvironment = FirstNonEmpty(environment, connection.Environment);
string resolvedPath = FirstNonEmpty(path, connection.DefaultSecretPath, "/");
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(resolvedEnvironment)) { throw new InfisicalConfigurationException("Environment is required."); }
if (string.IsNullOrEmpty(projectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(environment)) { throw new InfisicalConfigurationException("Environment is required."); }
if (string.IsNullOrEmpty(folderId)) { throw new InfisicalConfigurationException("FolderId is required."); }
string resolvedPath = FirstNonEmpty(path, "/");
if (string.IsNullOrEmpty(name)) { throw new InfisicalConfigurationException("Name is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "folderId", folderId } };
InfisicalFolderUpdateRequestDto request = new InfisicalFolderUpdateRequestDto
{
WorkspaceId = resolvedProjectId,
Environment = resolvedEnvironment,
WorkspaceId = projectId,
Environment = environment,
Name = name,
Path = resolvedPath
};
@@ -142,7 +136,7 @@ namespace PSInfisicalAPI.Folders
InfisicalFolderSingleResponseDto dto = _serializer.Deserialize<InfisicalFolderSingleResponseDto>(response.Body);
response.Clear();
InfisicalFolder mapped = InfisicalFolderMapper.Map(dto != null ? dto.Folder : null, resolvedProjectId, resolvedEnvironment);
InfisicalFolder mapped = InfisicalFolderMapper.Map(dto != null ? dto.Folder : null, projectId, environment);
_logger.Information(Component, "Infisical folder update was successful.");
return mapped;
}
@@ -156,18 +150,16 @@ namespace PSInfisicalAPI.Folders
public void Delete(InfisicalConnection connection, string projectId, string environment, string folderId, string path)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
string resolvedEnvironment = FirstNonEmpty(environment, connection.Environment);
string resolvedPath = FirstNonEmpty(path, connection.DefaultSecretPath, "/");
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(resolvedEnvironment)) { throw new InfisicalConfigurationException("Environment is required."); }
if (string.IsNullOrEmpty(projectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(environment)) { throw new InfisicalConfigurationException("Environment is required."); }
if (string.IsNullOrEmpty(folderId)) { throw new InfisicalConfigurationException("FolderId is required."); }
string resolvedPath = FirstNonEmpty(path, "/");
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "folderId", folderId } };
List<KeyValuePair<string, string>> queryParameters = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("workspaceId", resolvedProjectId),
new KeyValuePair<string, string>("environment", resolvedEnvironment),
new KeyValuePair<string, string>("workspaceId", projectId),
new KeyValuePair<string, string>("environment", environment),
new KeyValuePair<string, string>("path", resolvedPath)
};
+20 -9
View File
@@ -23,7 +23,8 @@ namespace PSInfisicalAPI.Http
string operationName,
IDictionary<string, string> pathParameters,
IEnumerable<KeyValuePair<string, string>> queryParameters,
string body)
string body,
IDictionary<string, string> extraHeaders = null)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
if (string.IsNullOrEmpty(endpointName)) { throw new ArgumentNullException(nameof(endpointName)); }
@@ -31,7 +32,7 @@ namespace PSInfisicalAPI.Http
InfisicalEndpointDefinition definition = InfisicalEndpointRegistry.Get(endpointName);
Uri uri = InfisicalUriBuilder.Build(connection.BaseUri, definition, pathParameters, queryParameters);
InfisicalHttpResponse response = ExecuteAuthorized(connection, definition, operationName, uri, body);
InfisicalHttpResponse response = ExecuteAuthorized(connection, definition, operationName, uri, body, extraHeaders);
if (response.StatusCode >= 200 && response.StatusCode < 300)
{
@@ -49,7 +50,8 @@ namespace PSInfisicalAPI.Http
string operationName,
IDictionary<string, string> pathParameters,
IEnumerable<KeyValuePair<string, string>> queryParameters,
string body)
string body,
IDictionary<string, string> extraHeaders = null)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
if (string.IsNullOrEmpty(endpointName)) { throw new ArgumentNullException(nameof(endpointName)); }
@@ -61,7 +63,7 @@ namespace PSInfisicalAPI.Http
{
InfisicalEndpointDefinition definition = candidates[index];
Uri uri = InfisicalUriBuilder.Build(connection.BaseUri, definition, pathParameters, queryParameters);
InfisicalHttpResponse response = ExecuteAuthorized(connection, definition, operationName, uri, body);
InfisicalHttpResponse response = ExecuteAuthorized(connection, definition, operationName, uri, body, extraHeaders);
if (response.StatusCode >= 200 && response.StatusCode < 300)
{
@@ -95,7 +97,8 @@ namespace PSInfisicalAPI.Http
InfisicalEndpointDefinition definition,
string operationName,
Uri uri,
string body)
string body,
IDictionary<string, string> extraHeaders = null)
{
Dictionary<string, string> headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
headers["Accept"] = "application/json";
@@ -118,6 +121,15 @@ namespace PSInfisicalAPI.Http
});
}
if (extraHeaders != null)
{
foreach (KeyValuePair<string, string> entry in extraHeaders)
{
if (string.IsNullOrEmpty(entry.Key)) { continue; }
headers[entry.Key] = entry.Value;
}
}
InfisicalHttpRequest request = new InfisicalHttpRequest
{
OperationName = operationName,
@@ -135,15 +147,14 @@ namespace PSInfisicalAPI.Http
private static InfisicalApiException BuildApiException(InfisicalHttpResponse response, InfisicalEndpointDefinition definition)
{
InfisicalApiException exception = new InfisicalApiException(string.Concat(
"Infisical API returned ",
response.StatusCode.ToString(CultureInfo.InvariantCulture),
" (", response.ReasonPhrase ?? string.Empty, ")."));
string message = InfisicalApiErrorEnvelope.BuildExceptionMessage(response.StatusCode, response.ReasonPhrase, response.Body);
InfisicalApiException exception = new InfisicalApiException(message);
exception.StatusCode = response.StatusCode;
exception.ReasonPhrase = response.ReasonPhrase;
exception.EndpointName = definition.Name;
exception.RequestMethod = definition.Method;
exception.SanitizedBody = response.Body;
InfisicalApiErrorEnvelope.Enrich(exception, response.Body);
return exception;
}
}
@@ -0,0 +1,31 @@
using System;
namespace PSInfisicalAPI.Models
{
public sealed class InfisicalCertificateApplication
{
public string Id { get; set; }
public string ProjectId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int? ProfileCount { get; set; }
public int? MemberCount { get; set; }
public int? CertificateCount { get; set; }
public DateTimeOffset? CreatedAtUtc { get; set; }
public DateTimeOffset? UpdatedAtUtc { get; set; }
}
public sealed class InfisicalCertificateApplicationProfileAttachment
{
public string ApplicationId { get; set; }
public string ProfileId { get; set; }
public string ProfileSlug { get; set; }
public string ProfileDescription { get; set; }
public string ApiConfigId { get; set; }
public string EstConfigId { get; set; }
public string AcmeConfigId { get; set; }
public string ScepConfigId { get; set; }
public DateTimeOffset? CreatedAtUtc { get; set; }
public DateTimeOffset? UpdatedAtUtc { get; set; }
}
}
@@ -0,0 +1,55 @@
using System;
namespace PSInfisicalAPI.Models
{
public sealed class InfisicalCertificateApplicationEnrollment
{
public string ApplicationId { get; set; }
public string ProfileId { get; set; }
public InfisicalCertificateApplicationApiEnrollment Api { get; set; }
public InfisicalCertificateApplicationEstEnrollment Est { get; set; }
public InfisicalCertificateApplicationAcmeEnrollment Acme { get; set; }
public InfisicalCertificateApplicationScepEnrollment Scep { get; set; }
public bool ApiConfigured { get { return Api != null; } }
public bool EstConfigured { get; set; }
public bool AcmeConfigured { get; set; }
public bool ScepConfigured { get; set; }
}
public sealed class InfisicalCertificateApplicationApiEnrollment
{
public string Id { get; set; }
public bool? AutoRenew { get; set; }
public int? RenewBeforeDays { get; set; }
}
public sealed class InfisicalCertificateApplicationEstEnrollment
{
public string Id { get; set; }
public bool? DisableBootstrapCaValidation { get; set; }
public string EstEndpointUrl { get; set; }
}
public sealed class InfisicalCertificateApplicationAcmeEnrollment
{
public string Id { get; set; }
public bool? SkipDnsOwnershipVerification { get; set; }
public bool? SkipEabBinding { get; set; }
public string DirectoryUrl { get; set; }
}
public sealed class InfisicalCertificateApplicationScepEnrollment
{
public string Id { get; set; }
public string ChallengeType { get; set; }
public bool? IncludeCaCertInResponse { get; set; }
public bool? AllowCertBasedRenewal { get; set; }
public int? DynamicChallengeExpiryMinutes { get; set; }
public int? DynamicChallengeMaxPending { get; set; }
public string ScepEndpointUrl { get; set; }
public string ChallengeEndpointUrl { get; set; }
public string RaCertificatePem { get; set; }
public string RaCertificateThumbprint { get; set; }
public DateTimeOffset? RaCertExpiresAtUtc { get; set; }
}
}
@@ -0,0 +1,50 @@
using System;
namespace PSInfisicalAPI.Models
{
public sealed class InfisicalCertificatePolicy
{
public string Id { get; set; }
public string ProjectId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public InfisicalCertificatePolicySubject Subject { get; set; }
public InfisicalCertificatePolicySan[] Sans { get; set; }
public InfisicalCertificatePolicyUsages KeyUsages { get; set; }
public InfisicalCertificatePolicyUsages ExtendedKeyUsages { get; set; }
public InfisicalCertificatePolicyAlgorithms Algorithms { get; set; }
public InfisicalCertificatePolicyValidity Validity { get; set; }
public DateTimeOffset? CreatedAtUtc { get; set; }
public DateTimeOffset? UpdatedAtUtc { get; set; }
}
public sealed class InfisicalCertificatePolicySubject
{
public string Type { get; set; }
public string[] Allowed { get; set; }
}
public sealed class InfisicalCertificatePolicySan
{
public string Type { get; set; }
public string[] Allowed { get; set; }
public string[] Required { get; set; }
}
public sealed class InfisicalCertificatePolicyUsages
{
public string[] Allowed { get; set; }
public string[] Required { get; set; }
}
public sealed class InfisicalCertificatePolicyAlgorithms
{
public string Signature { get; set; }
public string[] KeyAlgorithms { get; set; }
}
public sealed class InfisicalCertificatePolicyValidity
{
public string Max { get; set; }
}
}
@@ -0,0 +1,58 @@
using System;
namespace PSInfisicalAPI.Models
{
public sealed class InfisicalCertificateProfile
{
public string Id { get; set; }
public string ProjectId { get; set; }
public string CaId { get; set; }
public string CertificatePolicyId { get; set; }
public string Slug { get; set; }
public string Description { get; set; }
public string EnrollmentType { get; set; }
public string IssuerType { get; set; }
public string EstConfigId { get; set; }
public string ApiConfigId { get; set; }
public string AcmeConfigId { get; set; }
public string ScepConfigId { get; set; }
public DateTimeOffset? CreatedAtUtc { get; set; }
public DateTimeOffset? UpdatedAtUtc { get; set; }
public InfisicalCertificateProfileDefaults Defaults { get; set; }
public InfisicalCertificateAuthoritySummary CertificateAuthority { get; set; }
public InfisicalCertificatePolicySummary CertificatePolicy { get; set; }
public InfisicalCertificateProfileApiConfig ApiConfig { get; set; }
}
public sealed class InfisicalCertificateProfileDefaults
{
public int? TtlDays { get; set; }
public string KeyAlgorithm { get; set; }
public string SignatureAlgorithm { get; set; }
public string[] KeyUsages { get; set; }
public string[] ExtendedKeyUsages { get; set; }
}
public sealed class InfisicalCertificateAuthoritySummary
{
public string Id { get; set; }
public string Status { get; set; }
public string Name { get; set; }
public bool? IsExternal { get; set; }
public string ExternalType { get; set; }
}
public sealed class InfisicalCertificatePolicySummary
{
public string Id { get; set; }
public string ProjectId { get; set; }
public string Name { get; set; }
}
public sealed class InfisicalCertificateProfileApiConfig
{
public string Id { get; set; }
public bool? AutoRenew { get; set; }
public int? RenewBeforeDays { get; set; }
}
}
@@ -0,0 +1,26 @@
using System.Collections.Generic;
using System.Security.Cryptography.X509Certificates;
namespace PSInfisicalAPI.Models
{
public sealed class InfisicalCertificateResult
{
public X509Certificate2 Leaf { get; set; }
public X509Certificate2[] Intermediates { get; set; }
public X509Certificate2 Root { get; set; }
public X509Certificate2[] Chain { get; set; }
public string SerialNumber { get; set; }
public string CertificatePem { get; set; }
public string CertificateChainPem { get; set; }
public string PrivateKeyPem { get; set; }
public string Status { get; set; }
public string StatusMessage { get; set; }
public string CertificateRequestId { get; set; }
public override string ToString()
{
if (Leaf != null) { return Leaf.Subject; }
return SerialNumber;
}
}
}
@@ -0,0 +1,37 @@
using System;
namespace PSInfisicalAPI.Models
{
public sealed class InfisicalPkiSubscriber
{
public string Id { get; set; }
public string ProjectId { get; set; }
public string CaId { get; set; }
public string Name { get; set; }
public string CommonName { get; set; }
public string Status { get; set; }
public string Ttl { get; set; }
public string[] SubjectAlternativeNames { get; set; }
public string[] KeyUsages { get; set; }
public string[] ExtendedKeyUsages { get; set; }
public bool? EnableAutoRenewal { get; set; }
public int? AutoRenewalPeriodInDays { get; set; }
public string LastOperationStatus { get; set; }
public string LastOperationMessage { get; set; }
public DateTimeOffset? LastOperationAtUtc { get; set; }
public DateTimeOffset? CreatedAtUtc { get; set; }
public DateTimeOffset? UpdatedAtUtc { get; set; }
public InfisicalPkiSubscriberProperties Properties { get; set; }
}
public sealed class InfisicalPkiSubscriberProperties
{
public string AzureTemplateType { get; set; }
public string Organization { get; set; }
public string OrganizationalUnit { get; set; }
public string Country { get; set; }
public string State { get; set; }
public string Locality { get; set; }
public string EmailAddress { get; set; }
}
}
@@ -0,0 +1,146 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace PSInfisicalAPI.Models
{
public sealed class InfisicalScepMdmProfile
{
private const string SyncMlMetInfNamespace = "syncml:metinf";
public string UniqueId { get; set; }
public string Scope { get; set; }
public string ServerUrl { get; set; }
public string Challenge { get; set; }
public string SubjectName { get; set; }
public string SubjectAlternativeNames { get; set; }
public string EkuMapping { get; set; }
public int? KeyUsage { get; set; }
public int? KeyLength { get; set; }
public string KeyAlgorithm { get; set; }
public string HashAlgorithm { get; set; }
public int? KeyProtection { get; set; }
public string ContainerName { get; set; }
public string ValidPeriod { get; set; }
public int? ValidPeriodUnits { get; set; }
public int? RetryCount { get; set; }
public int? RetryDelay { get; set; }
public string TemplateName { get; set; }
public string CAThumbprint { get; set; }
public string CustomTextToShowInPrompt { get; set; }
public string SourceProfileId { get; set; }
public string SourceProfileSlug { get; set; }
public string ToSyncMl()
{
if (string.IsNullOrEmpty(UniqueId)) { throw new InvalidOperationException("UniqueId is required."); }
if (string.IsNullOrEmpty(ServerUrl)) { throw new InvalidOperationException("ServerUrl is required."); }
string scopeSegment = string.Equals(Scope, "User", StringComparison.OrdinalIgnoreCase) ? "./User" : "./Device";
string nodeBase = string.Concat(scopeSegment, "/Vendor/MSFT/ClientCertificateInstall/SCEP/", UniqueId, "/Install/");
List<CspNode> nodes = new List<CspNode>();
AddString(nodes, "ServerURL", ServerUrl);
AddString(nodes, "Challenge", Challenge);
AddString(nodes, "SubjectName", SubjectName);
AddString(nodes, "SubjectAlternativeNames", SubjectAlternativeNames);
AddString(nodes, "EKUMapping", EkuMapping);
AddInt(nodes, "KeyUsage", KeyUsage);
AddInt(nodes, "KeyLength", KeyLength);
AddString(nodes, "KeyAlgorithm", KeyAlgorithm);
AddString(nodes, "HashAlgorithm", HashAlgorithm);
AddInt(nodes, "KeyProtection", KeyProtection);
AddString(nodes, "ContainerName", ContainerName);
AddString(nodes, "ValidPeriod", ValidPeriod);
AddInt(nodes, "ValidPeriodUnits", ValidPeriodUnits);
AddInt(nodes, "RetryCount", RetryCount);
AddInt(nodes, "RetryDelay", RetryDelay);
AddString(nodes, "TemplateName", TemplateName);
AddString(nodes, "CAThumbprint", CAThumbprint);
AddString(nodes, "CustomTextToShowInPrompt", CustomTextToShowInPrompt);
XDocument document = new XDocument(new XDeclaration("1.0", "utf-8", null));
XElement syncBody = new XElement("SyncBody");
XElement atomic = new XElement("Atomic", new XElement("CmdID", "1"));
int cmdId = 2;
foreach (CspNode node in nodes)
{
XElement meta = new XElement("Meta", new XElement(XName.Get("Format", SyncMlMetInfNamespace), node.Format));
XElement item = new XElement("Item",
new XElement("Target", new XElement("LocURI", string.Concat(nodeBase, node.Suffix))),
meta,
new XElement("Data", node.Value));
atomic.Add(new XElement("Replace", new XElement("CmdID", cmdId.ToString(System.Globalization.CultureInfo.InvariantCulture)), item));
cmdId++;
}
XElement enrollItem = new XElement("Item",
new XElement("Target", new XElement("LocURI", string.Concat(nodeBase, "Enroll"))),
new XElement("Meta", new XElement(XName.Get("Format", SyncMlMetInfNamespace), "node")));
atomic.Add(new XElement("Exec", new XElement("CmdID", cmdId.ToString(System.Globalization.CultureInfo.InvariantCulture)), enrollItem));
syncBody.Add(atomic);
document.Add(syncBody);
XmlWriterSettings writerSettings = new XmlWriterSettings
{
Indent = true,
IndentChars = " ",
NewLineHandling = NewLineHandling.Replace,
Encoding = new UTF8Encoding(false),
OmitXmlDeclaration = false,
CloseOutput = false
};
string serialized;
using (MemoryStream buffer = new MemoryStream())
{
using (XmlWriter writer = XmlWriter.Create(buffer, writerSettings))
{
document.Save(writer);
}
serialized = writerSettings.Encoding.GetString(buffer.ToArray());
}
using (StringReader stringReader = new StringReader(serialized))
{
XmlReaderSettings readerSettings = new XmlReaderSettings { DtdProcessing = DtdProcessing.Prohibit, XmlResolver = null };
using (XmlReader reader = XmlReader.Create(stringReader, readerSettings))
{
XDocument.Load(reader);
}
}
return serialized;
}
private static void AddString(List<CspNode> nodes, string suffix, string value)
{
if (string.IsNullOrEmpty(value)) { return; }
nodes.Add(new CspNode { Suffix = suffix, Value = value, Format = "chr" });
}
private static void AddInt(List<CspNode> nodes, string suffix, int? value)
{
if (!value.HasValue) { return; }
nodes.Add(new CspNode { Suffix = suffix, Value = value.Value.ToString(System.Globalization.CultureInfo.InvariantCulture), Format = "int" });
}
private sealed class CspNode
{
public string Suffix;
public string Value;
public string Format;
}
}
}
@@ -0,0 +1,19 @@
namespace PSInfisicalAPI.Models
{
public sealed class InfisicalSignedCertificate
{
public string SerialNumber { get; set; }
public string CertificatePem { get; set; }
public string CertificateChainPem { get; set; }
public string IssuingCaCertificatePem { get; set; }
public string PrivateKeyPem { get; set; }
public string Status { get; set; }
public string StatusMessage { get; set; }
public string CertificateRequestId { get; set; }
public override string ToString()
{
return SerialNumber;
}
}
}
+21
View File
@@ -3,6 +3,26 @@ using Newtonsoft.Json;
namespace PSInfisicalAPI.Pki
{
internal sealed class InfisicalInternalCaConfigurationDto
{
[JsonProperty("type")] public string Type { get; set; }
[JsonProperty("friendlyName")] public string FriendlyName { get; set; }
[JsonProperty("commonName")] public string CommonName { get; set; }
[JsonProperty("organization")] public string OrganizationName { get; set; }
[JsonProperty("ou")] public string OrganizationUnit { get; set; }
[JsonProperty("country")] public string Country { get; set; }
[JsonProperty("province")] public string State { get; set; }
[JsonProperty("locality")] public string Locality { get; set; }
[JsonProperty("notBefore")] public string NotBefore { get; set; }
[JsonProperty("notAfter")] public string NotAfter { get; set; }
[JsonProperty("maxPathLength")] public int? MaxPathLength { get; set; }
[JsonProperty("keyAlgorithm")] public string KeyAlgorithm { get; set; }
[JsonProperty("dn")] public string DistinguishedName { get; set; }
[JsonProperty("parentCaId")] public string ParentCaId { get; set; }
[JsonProperty("serialNumber")] public string SerialNumber { get; set; }
[JsonProperty("activeCaCertId")] public string ActiveCaCertId { get; set; }
}
internal sealed class InfisicalInternalCaResponseDto
{
[JsonProperty("id")] public string Id { get; set; }
@@ -28,6 +48,7 @@ namespace PSInfisicalAPI.Pki
[JsonProperty("activeCaCertId")] public string ActiveCaCertId { get; set; }
[JsonProperty("createdAt")] public string CreatedAt { get; set; }
[JsonProperty("updatedAt")] public string UpdatedAt { get; set; }
[JsonProperty("configuration")] public InfisicalInternalCaConfigurationDto Configuration { get; set; }
}
internal sealed class InfisicalInternalCaListResponseDto
+23 -16
View File
@@ -14,34 +14,41 @@ namespace PSInfisicalAPI.Pki
return null;
}
InfisicalInternalCaConfigurationDto cfg = dto.Configuration;
return new InfisicalCertificateAuthority
{
Id = dto.Id,
ProjectId = !string.IsNullOrEmpty(dto.ProjectId) ? dto.ProjectId : fallbackProjectId,
Name = dto.Name,
FriendlyName = dto.FriendlyName,
Type = dto.Type,
FriendlyName = Coalesce(cfg != null ? cfg.FriendlyName : null, dto.FriendlyName),
Type = Coalesce(dto.Type, cfg != null ? cfg.Type : null),
Status = dto.Status,
EnableDirectIssuance = dto.EnableDirectIssuance,
KeyAlgorithm = dto.KeyAlgorithm,
DistinguishedName = dto.DistinguishedName,
OrganizationName = dto.OrganizationName,
OrganizationUnit = dto.OrganizationUnit,
Country = dto.Country,
State = dto.State,
Locality = dto.Locality,
CommonName = dto.CommonName,
MaxPathLength = dto.MaxPathLength,
NotBefore = dto.NotBefore,
NotAfter = dto.NotAfter,
SerialNumber = dto.SerialNumber,
ParentCaId = dto.ParentCaId,
ActiveCaCertId = dto.ActiveCaCertId,
KeyAlgorithm = Coalesce(cfg != null ? cfg.KeyAlgorithm : null, dto.KeyAlgorithm),
DistinguishedName = Coalesce(cfg != null ? cfg.DistinguishedName : null, dto.DistinguishedName),
OrganizationName = Coalesce(cfg != null ? cfg.OrganizationName : null, dto.OrganizationName),
OrganizationUnit = Coalesce(cfg != null ? cfg.OrganizationUnit : null, dto.OrganizationUnit),
Country = Coalesce(cfg != null ? cfg.Country : null, dto.Country),
State = Coalesce(cfg != null ? cfg.State : null, dto.State),
Locality = Coalesce(cfg != null ? cfg.Locality : null, dto.Locality),
CommonName = Coalesce(cfg != null ? cfg.CommonName : null, dto.CommonName),
MaxPathLength = (cfg != null && cfg.MaxPathLength.HasValue) ? cfg.MaxPathLength : dto.MaxPathLength,
NotBefore = Coalesce(cfg != null ? cfg.NotBefore : null, dto.NotBefore),
NotAfter = Coalesce(cfg != null ? cfg.NotAfter : null, dto.NotAfter),
SerialNumber = Coalesce(cfg != null ? cfg.SerialNumber : null, dto.SerialNumber),
ParentCaId = Coalesce(cfg != null ? cfg.ParentCaId : null, dto.ParentCaId),
ActiveCaCertId = Coalesce(cfg != null ? cfg.ActiveCaCertId : null, dto.ActiveCaCertId),
CreatedAtUtc = ParseTimestamp(dto.CreatedAt),
UpdatedAtUtc = ParseTimestamp(dto.UpdatedAt)
};
}
private static string Coalesce(string primary, string fallback)
{
return !string.IsNullOrEmpty(primary) ? primary : fallback;
}
public static InfisicalCertificateAuthority[] MapMany(IEnumerable<InfisicalInternalCaResponseDto> items, string fallbackProjectId)
{
if (items == null)
@@ -0,0 +1,92 @@
using System.Collections.Generic;
using Newtonsoft.Json;
namespace PSInfisicalAPI.Pki
{
internal sealed class InfisicalCertificateApplicationResponseDto
{
[JsonProperty("id")] public string Id { get; set; }
[JsonProperty("projectId")] public string ProjectId { get; set; }
[JsonProperty("name")] public string Name { get; set; }
[JsonProperty("description")] public string Description { get; set; }
[JsonProperty("profileCount")] public int? ProfileCount { get; set; }
[JsonProperty("memberCount")] public int? MemberCount { get; set; }
[JsonProperty("certificateCount")] public int? CertificateCount { get; set; }
[JsonProperty("createdAt")] public string CreatedAt { get; set; }
[JsonProperty("updatedAt")] public string UpdatedAt { get; set; }
}
internal sealed class InfisicalCertificateApplicationListResponseDto
{
[JsonProperty("applications")] public List<InfisicalCertificateApplicationResponseDto> Applications { get; set; }
[JsonProperty("total")] public int? Total { get; set; }
}
internal sealed class InfisicalCertificateApplicationProfileAttachmentDto
{
[JsonProperty("applicationId")] public string ApplicationId { get; set; }
[JsonProperty("profileId")] public string ProfileId { get; set; }
[JsonProperty("profileSlug")] public string ProfileSlug { get; set; }
[JsonProperty("profileDescription")] public string ProfileDescription { get; set; }
[JsonProperty("apiConfigId")] public string ApiConfigId { get; set; }
[JsonProperty("estConfigId")] public string EstConfigId { get; set; }
[JsonProperty("acmeConfigId")] public string AcmeConfigId { get; set; }
[JsonProperty("scepConfigId")] public string ScepConfigId { get; set; }
[JsonProperty("createdAt")] public string CreatedAt { get; set; }
[JsonProperty("updatedAt")] public string UpdatedAt { get; set; }
}
internal sealed class InfisicalCertificateApplicationProfilesResponseDto
{
[JsonProperty("profiles")] public List<InfisicalCertificateApplicationProfileAttachmentDto> Profiles { get; set; }
}
internal sealed class InfisicalCertificateApplicationEnrollmentResponseDto
{
[JsonProperty("applicationId")] public string ApplicationId { get; set; }
[JsonProperty("profileId")] public string ProfileId { get; set; }
[JsonProperty("api")] public InfisicalCertificateApplicationApiEnrollmentDto Api { get; set; }
[JsonProperty("est")] public InfisicalCertificateApplicationEstEnrollmentDto Est { get; set; }
[JsonProperty("acme")] public InfisicalCertificateApplicationAcmeEnrollmentDto Acme { get; set; }
[JsonProperty("scep")] public InfisicalCertificateApplicationScepEnrollmentDto Scep { get; set; }
[JsonProperty("estConfigured")] public bool? EstConfigured { get; set; }
[JsonProperty("acmeConfigured")] public bool? AcmeConfigured { get; set; }
[JsonProperty("scepConfigured")] public bool? ScepConfigured { get; set; }
}
internal sealed class InfisicalCertificateApplicationApiEnrollmentDto
{
[JsonProperty("id")] public string Id { get; set; }
[JsonProperty("autoRenew")] public bool? AutoRenew { get; set; }
[JsonProperty("renewBeforeDays")] public int? RenewBeforeDays { get; set; }
}
internal sealed class InfisicalCertificateApplicationEstEnrollmentDto
{
[JsonProperty("id")] public string Id { get; set; }
[JsonProperty("disableBootstrapCaValidation")] public bool? DisableBootstrapCaValidation { get; set; }
[JsonProperty("estEndpointUrl")] public string EstEndpointUrl { get; set; }
}
internal sealed class InfisicalCertificateApplicationAcmeEnrollmentDto
{
[JsonProperty("id")] public string Id { get; set; }
[JsonProperty("skipDnsOwnershipVerification")] public bool? SkipDnsOwnershipVerification { get; set; }
[JsonProperty("skipEabBinding")] public bool? SkipEabBinding { get; set; }
[JsonProperty("directoryUrl")] public string DirectoryUrl { get; set; }
}
internal sealed class InfisicalCertificateApplicationScepEnrollmentDto
{
[JsonProperty("id")] public string Id { get; set; }
[JsonProperty("challengeType")] public string ChallengeType { get; set; }
[JsonProperty("includeCaCertInResponse")] public bool? IncludeCaCertInResponse { get; set; }
[JsonProperty("allowCertBasedRenewal")] public bool? AllowCertBasedRenewal { get; set; }
[JsonProperty("dynamicChallengeExpiryMinutes")] public int? DynamicChallengeExpiryMinutes { get; set; }
[JsonProperty("dynamicChallengeMaxPending")] public int? DynamicChallengeMaxPending { get; set; }
[JsonProperty("scepEndpointUrl")] public string ScepEndpointUrl { get; set; }
[JsonProperty("challengeEndpointUrl")] public string ChallengeEndpointUrl { get; set; }
[JsonProperty("raCertificatePem")] public string RaCertificatePem { get; set; }
[JsonProperty("raCertExpiresAt")] public string RaCertExpiresAt { get; set; }
}
}
@@ -0,0 +1,175 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using PSInfisicalAPI.Models;
namespace PSInfisicalAPI.Pki
{
internal static class InfisicalCertificateApplicationMapper
{
public static InfisicalCertificateApplication Map(InfisicalCertificateApplicationResponseDto dto, string fallbackProjectId)
{
if (dto == null) { return null; }
return new InfisicalCertificateApplication
{
Id = dto.Id,
ProjectId = !string.IsNullOrEmpty(dto.ProjectId) ? dto.ProjectId : fallbackProjectId,
Name = dto.Name,
Description = dto.Description,
ProfileCount = dto.ProfileCount,
MemberCount = dto.MemberCount,
CertificateCount = dto.CertificateCount,
CreatedAtUtc = ParseTimestamp(dto.CreatedAt),
UpdatedAtUtc = ParseTimestamp(dto.UpdatedAt)
};
}
public static InfisicalCertificateApplication[] MapMany(IEnumerable<InfisicalCertificateApplicationResponseDto> items, string fallbackProjectId)
{
if (items == null) { return Array.Empty<InfisicalCertificateApplication>(); }
List<InfisicalCertificateApplication> results = new List<InfisicalCertificateApplication>();
foreach (InfisicalCertificateApplicationResponseDto dto in items)
{
InfisicalCertificateApplication mapped = Map(dto, fallbackProjectId);
if (mapped != null) { results.Add(mapped); }
}
return results.ToArray();
}
public static InfisicalCertificateApplicationProfileAttachment MapAttachment(InfisicalCertificateApplicationProfileAttachmentDto dto)
{
if (dto == null) { return null; }
return new InfisicalCertificateApplicationProfileAttachment
{
ApplicationId = dto.ApplicationId,
ProfileId = dto.ProfileId,
ProfileSlug = dto.ProfileSlug,
ProfileDescription = dto.ProfileDescription,
ApiConfigId = dto.ApiConfigId,
EstConfigId = dto.EstConfigId,
AcmeConfigId = dto.AcmeConfigId,
ScepConfigId = dto.ScepConfigId,
CreatedAtUtc = ParseTimestamp(dto.CreatedAt),
UpdatedAtUtc = ParseTimestamp(dto.UpdatedAt)
};
}
public static InfisicalCertificateApplicationProfileAttachment[] MapAttachments(IEnumerable<InfisicalCertificateApplicationProfileAttachmentDto> items)
{
if (items == null) { return Array.Empty<InfisicalCertificateApplicationProfileAttachment>(); }
List<InfisicalCertificateApplicationProfileAttachment> results = new List<InfisicalCertificateApplicationProfileAttachment>();
foreach (InfisicalCertificateApplicationProfileAttachmentDto dto in items)
{
InfisicalCertificateApplicationProfileAttachment mapped = MapAttachment(dto);
if (mapped != null) { results.Add(mapped); }
}
return results.ToArray();
}
public static InfisicalCertificateApplicationEnrollment MapEnrollment(InfisicalCertificateApplicationEnrollmentResponseDto dto)
{
if (dto == null) { return null; }
return new InfisicalCertificateApplicationEnrollment
{
ApplicationId = dto.ApplicationId,
ProfileId = dto.ProfileId,
Api = MapApi(dto.Api),
Est = MapEst(dto.Est),
Acme = MapAcme(dto.Acme),
Scep = MapScep(dto.Scep),
EstConfigured = dto.EstConfigured.GetValueOrDefault(),
AcmeConfigured = dto.AcmeConfigured.GetValueOrDefault(),
ScepConfigured = dto.ScepConfigured.GetValueOrDefault()
};
}
private static InfisicalCertificateApplicationApiEnrollment MapApi(InfisicalCertificateApplicationApiEnrollmentDto dto)
{
if (dto == null) { return null; }
return new InfisicalCertificateApplicationApiEnrollment { Id = dto.Id, AutoRenew = dto.AutoRenew, RenewBeforeDays = dto.RenewBeforeDays };
}
private static InfisicalCertificateApplicationEstEnrollment MapEst(InfisicalCertificateApplicationEstEnrollmentDto dto)
{
if (dto == null) { return null; }
return new InfisicalCertificateApplicationEstEnrollment { Id = dto.Id, DisableBootstrapCaValidation = dto.DisableBootstrapCaValidation, EstEndpointUrl = dto.EstEndpointUrl };
}
private static InfisicalCertificateApplicationAcmeEnrollment MapAcme(InfisicalCertificateApplicationAcmeEnrollmentDto dto)
{
if (dto == null) { return null; }
return new InfisicalCertificateApplicationAcmeEnrollment { Id = dto.Id, SkipDnsOwnershipVerification = dto.SkipDnsOwnershipVerification, SkipEabBinding = dto.SkipEabBinding, DirectoryUrl = dto.DirectoryUrl };
}
private static InfisicalCertificateApplicationScepEnrollment MapScep(InfisicalCertificateApplicationScepEnrollmentDto dto)
{
if (dto == null) { return null; }
return new InfisicalCertificateApplicationScepEnrollment
{
Id = dto.Id,
ChallengeType = dto.ChallengeType,
IncludeCaCertInResponse = dto.IncludeCaCertInResponse,
AllowCertBasedRenewal = dto.AllowCertBasedRenewal,
DynamicChallengeExpiryMinutes = dto.DynamicChallengeExpiryMinutes,
DynamicChallengeMaxPending = dto.DynamicChallengeMaxPending,
ScepEndpointUrl = dto.ScepEndpointUrl,
ChallengeEndpointUrl = dto.ChallengeEndpointUrl,
RaCertificatePem = dto.RaCertificatePem,
RaCertificateThumbprint = ComputeThumbprint(dto.RaCertificatePem),
RaCertExpiresAtUtc = ParseTimestamp(dto.RaCertExpiresAt)
};
}
internal static string ComputeThumbprint(string pem)
{
if (string.IsNullOrEmpty(pem)) { return null; }
try
{
byte[] der = Convert.FromBase64String(StripPemArmor(pem));
using (X509Certificate2 cert = new X509Certificate2(der))
{
return cert.Thumbprint;
}
}
catch
{
return null;
}
}
private static string StripPemArmor(string pem)
{
StringBuilder sb = new StringBuilder(pem.Length);
using (StringReader reader = new StringReader(pem))
{
string line;
while ((line = reader.ReadLine()) != null)
{
string trimmed = line.Trim();
if (trimmed.Length == 0) { continue; }
if (trimmed.StartsWith("-----", StringComparison.Ordinal)) { continue; }
sb.Append(trimmed);
}
}
return sb.ToString();
}
private static DateTimeOffset? ParseTimestamp(string value)
{
if (string.IsNullOrEmpty(value)) { return null; }
DateTimeOffset parsed;
if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out parsed))
{
return parsed;
}
return null;
}
}
}
@@ -0,0 +1,58 @@
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace PSInfisicalAPI.Pki
{
internal sealed class InfisicalCertificatePolicyResponseDto
{
[JsonProperty("id")] public string Id { get; set; }
[JsonProperty("projectId")] public string ProjectId { get; set; }
[JsonProperty("name")] public string Name { get; set; }
[JsonProperty("description")] public string Description { get; set; }
[JsonProperty("subject")] public InfisicalCertificatePolicySubjectDto Subject { get; set; }
[JsonProperty("sans")] public JToken SansRaw { get; set; }
[JsonProperty("keyUsages")] public InfisicalCertificatePolicyUsagesDto KeyUsages { get; set; }
[JsonProperty("extendedKeyUsages")] public InfisicalCertificatePolicyUsagesDto ExtendedKeyUsages { get; set; }
[JsonProperty("algorithms")] public InfisicalCertificatePolicyAlgorithmsDto Algorithms { get; set; }
[JsonProperty("validity")] public InfisicalCertificatePolicyValidityDto Validity { get; set; }
[JsonProperty("createdAt")] public string CreatedAt { get; set; }
[JsonProperty("updatedAt")] public string UpdatedAt { get; set; }
}
internal sealed class InfisicalCertificatePolicySubjectDto
{
[JsonProperty("type")] public string Type { get; set; }
[JsonProperty("allowed")] public JToken AllowedRaw { get; set; }
}
internal sealed class InfisicalCertificatePolicySanDto
{
[JsonProperty("type")] public string Type { get; set; }
[JsonProperty("allowed")] public JToken AllowedRaw { get; set; }
[JsonProperty("required")] public JToken RequiredRaw { get; set; }
}
internal sealed class InfisicalCertificatePolicyUsagesDto
{
[JsonProperty("allowed")] public JToken AllowedRaw { get; set; }
[JsonProperty("required")] public JToken RequiredRaw { get; set; }
}
internal sealed class InfisicalCertificatePolicyAlgorithmsDto
{
[JsonProperty("signature")] public string Signature { get; set; }
[JsonProperty("keyAlgorithm")] public JToken KeyAlgorithmRaw { get; set; }
}
internal sealed class InfisicalCertificatePolicyValidityDto
{
[JsonProperty("max")] public string Max { get; set; }
}
internal sealed class InfisicalCertificatePolicyListResponseDto
{
[JsonProperty("certificatePolicies")] public List<InfisicalCertificatePolicyResponseDto> CertificatePolicies { get; set; }
[JsonProperty("totalCount")] public int? TotalCount { get; set; }
}
}
@@ -0,0 +1,138 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using Newtonsoft.Json.Linq;
using PSInfisicalAPI.Models;
namespace PSInfisicalAPI.Pki
{
internal static class InfisicalCertificatePolicyMapper
{
public static InfisicalCertificatePolicy Map(InfisicalCertificatePolicyResponseDto dto, string fallbackProjectId)
{
if (dto == null)
{
return null;
}
return new InfisicalCertificatePolicy
{
Id = dto.Id,
ProjectId = !string.IsNullOrEmpty(dto.ProjectId) ? dto.ProjectId : fallbackProjectId,
Name = dto.Name,
Description = dto.Description,
Subject = MapSubject(dto.Subject),
Sans = MapSans(dto.SansRaw),
KeyUsages = MapUsages(dto.KeyUsages),
ExtendedKeyUsages = MapUsages(dto.ExtendedKeyUsages),
Algorithms = MapAlgorithms(dto.Algorithms),
Validity = MapValidity(dto.Validity),
CreatedAtUtc = ParseTimestamp(dto.CreatedAt),
UpdatedAtUtc = ParseTimestamp(dto.UpdatedAt)
};
}
public static InfisicalCertificatePolicy[] MapMany(IEnumerable<InfisicalCertificatePolicyResponseDto> items, string fallbackProjectId)
{
if (items == null)
{
return Array.Empty<InfisicalCertificatePolicy>();
}
List<InfisicalCertificatePolicy> results = new List<InfisicalCertificatePolicy>();
foreach (InfisicalCertificatePolicyResponseDto dto in items)
{
InfisicalCertificatePolicy mapped = Map(dto, fallbackProjectId);
if (mapped != null)
{
results.Add(mapped);
}
}
return results.ToArray();
}
private static InfisicalCertificatePolicySubject MapSubject(InfisicalCertificatePolicySubjectDto dto)
{
if (dto == null) { return null; }
return new InfisicalCertificatePolicySubject
{
Type = dto.Type,
Allowed = InfisicalCertificateProfileMapper.FlattenStringOrStringArray(dto.AllowedRaw)
};
}
private static InfisicalCertificatePolicySan[] MapSans(JToken token)
{
if (token == null || token.Type == JTokenType.Null) { return null; }
List<InfisicalCertificatePolicySan> results = new List<InfisicalCertificatePolicySan>();
if (token.Type == JTokenType.Array)
{
foreach (JToken child in (JArray)token)
{
InfisicalCertificatePolicySan mapped = MapSanObject(child);
if (mapped != null) { results.Add(mapped); }
}
}
else if (token.Type == JTokenType.Object)
{
InfisicalCertificatePolicySan mapped = MapSanObject(token);
if (mapped != null) { results.Add(mapped); }
}
return results.Count > 0 ? results.ToArray() : null;
}
private static InfisicalCertificatePolicySan MapSanObject(JToken token)
{
if (token == null || token.Type != JTokenType.Object) { return null; }
InfisicalCertificatePolicySanDto dto = token.ToObject<InfisicalCertificatePolicySanDto>();
if (dto == null) { return null; }
return new InfisicalCertificatePolicySan
{
Type = dto.Type,
Allowed = InfisicalCertificateProfileMapper.FlattenStringOrStringArray(dto.AllowedRaw),
Required = InfisicalCertificateProfileMapper.FlattenStringOrStringArray(dto.RequiredRaw)
};
}
private static InfisicalCertificatePolicyUsages MapUsages(InfisicalCertificatePolicyUsagesDto dto)
{
if (dto == null) { return null; }
return new InfisicalCertificatePolicyUsages
{
Allowed = InfisicalCertificateProfileMapper.FlattenStringOrStringArray(dto.AllowedRaw),
Required = InfisicalCertificateProfileMapper.FlattenStringOrStringArray(dto.RequiredRaw)
};
}
private static InfisicalCertificatePolicyAlgorithms MapAlgorithms(InfisicalCertificatePolicyAlgorithmsDto dto)
{
if (dto == null) { return null; }
return new InfisicalCertificatePolicyAlgorithms
{
Signature = dto.Signature,
KeyAlgorithms = InfisicalCertificateProfileMapper.FlattenStringOrStringArray(dto.KeyAlgorithmRaw)
};
}
private static InfisicalCertificatePolicyValidity MapValidity(InfisicalCertificatePolicyValidityDto dto)
{
if (dto == null) { return null; }
return new InfisicalCertificatePolicyValidity { Max = dto.Max };
}
private static DateTimeOffset? ParseTimestamp(string value)
{
if (string.IsNullOrEmpty(value)) { return null; }
DateTimeOffset parsed;
if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out parsed))
{
return parsed;
}
return null;
}
}
}
@@ -0,0 +1,66 @@
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace PSInfisicalAPI.Pki
{
internal sealed class InfisicalCertificateProfileResponseDto
{
[JsonProperty("id")] public string Id { get; set; }
[JsonProperty("projectId")] public string ProjectId { get; set; }
[JsonProperty("caId")] public string CaId { get; set; }
[JsonProperty("certificatePolicyId")] public string CertificatePolicyId { get; set; }
[JsonProperty("slug")] public string Slug { get; set; }
[JsonProperty("description")] public string Description { get; set; }
[JsonProperty("enrollmentType")] public string EnrollmentType { get; set; }
[JsonProperty("issuerType")] public string IssuerType { get; set; }
[JsonProperty("estConfigId")] public string EstConfigId { get; set; }
[JsonProperty("apiConfigId")] public string ApiConfigId { get; set; }
[JsonProperty("acmeConfigId")] public string AcmeConfigId { get; set; }
[JsonProperty("scepConfigId")] public string ScepConfigId { get; set; }
[JsonProperty("createdAt")] public string CreatedAt { get; set; }
[JsonProperty("updatedAt")] public string UpdatedAt { get; set; }
[JsonProperty("defaults")] public InfisicalCertificateProfileDefaultsDto Defaults { get; set; }
[JsonProperty("certificateAuthority")] public InfisicalCertificateAuthoritySummaryDto CertificateAuthority { get; set; }
[JsonProperty("certificatePolicy")] public InfisicalCertificatePolicySummaryDto CertificatePolicy { get; set; }
[JsonProperty("apiConfig")] public InfisicalCertificateProfileApiConfigDto ApiConfig { get; set; }
}
internal sealed class InfisicalCertificateProfileDefaultsDto
{
[JsonProperty("ttlDays")] public int? TtlDays { get; set; }
[JsonProperty("keyAlgorithm")] public string KeyAlgorithm { get; set; }
[JsonProperty("signatureAlgorithm")] public string SignatureAlgorithm { get; set; }
[JsonProperty("keyUsages")] public JToken KeyUsagesRaw { get; set; }
[JsonProperty("extendedKeyUsages")] public JToken ExtendedKeyUsagesRaw { get; set; }
}
internal sealed class InfisicalCertificateAuthoritySummaryDto
{
[JsonProperty("id")] public string Id { get; set; }
[JsonProperty("status")] public string Status { get; set; }
[JsonProperty("name")] public string Name { get; set; }
[JsonProperty("isExternal")] public bool? IsExternal { get; set; }
[JsonProperty("externalType")] public string ExternalType { get; set; }
}
internal sealed class InfisicalCertificatePolicySummaryDto
{
[JsonProperty("id")] public string Id { get; set; }
[JsonProperty("projectId")] public string ProjectId { get; set; }
[JsonProperty("name")] public string Name { get; set; }
}
internal sealed class InfisicalCertificateProfileApiConfigDto
{
[JsonProperty("id")] public string Id { get; set; }
[JsonProperty("autoRenew")] public bool? AutoRenew { get; set; }
[JsonProperty("renewBeforeDays")] public int? RenewBeforeDays { get; set; }
}
internal sealed class InfisicalCertificateProfileListResponseDto
{
[JsonProperty("certificateProfiles")] public List<InfisicalCertificateProfileResponseDto> CertificateProfiles { get; set; }
[JsonProperty("totalCount")] public int? TotalCount { get; set; }
}
}
@@ -0,0 +1,155 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using Newtonsoft.Json.Linq;
using PSInfisicalAPI.Models;
namespace PSInfisicalAPI.Pki
{
internal static class InfisicalCertificateProfileMapper
{
public static InfisicalCertificateProfile Map(InfisicalCertificateProfileResponseDto dto, string fallbackProjectId)
{
if (dto == null)
{
return null;
}
return new InfisicalCertificateProfile
{
Id = dto.Id,
ProjectId = !string.IsNullOrEmpty(dto.ProjectId) ? dto.ProjectId : fallbackProjectId,
CaId = dto.CaId,
CertificatePolicyId = dto.CertificatePolicyId,
Slug = dto.Slug,
Description = dto.Description,
EnrollmentType = dto.EnrollmentType,
IssuerType = dto.IssuerType,
EstConfigId = dto.EstConfigId,
ApiConfigId = dto.ApiConfigId,
AcmeConfigId = dto.AcmeConfigId,
ScepConfigId = dto.ScepConfigId,
CreatedAtUtc = ParseTimestamp(dto.CreatedAt),
UpdatedAtUtc = ParseTimestamp(dto.UpdatedAt),
Defaults = MapDefaults(dto.Defaults),
CertificateAuthority = MapCa(dto.CertificateAuthority),
CertificatePolicy = MapPolicy(dto.CertificatePolicy),
ApiConfig = MapApiConfig(dto.ApiConfig)
};
}
public static InfisicalCertificateProfile[] MapMany(IEnumerable<InfisicalCertificateProfileResponseDto> items, string fallbackProjectId)
{
if (items == null)
{
return Array.Empty<InfisicalCertificateProfile>();
}
List<InfisicalCertificateProfile> results = new List<InfisicalCertificateProfile>();
foreach (InfisicalCertificateProfileResponseDto dto in items)
{
InfisicalCertificateProfile mapped = Map(dto, fallbackProjectId);
if (mapped != null)
{
results.Add(mapped);
}
}
return results.ToArray();
}
private static InfisicalCertificateProfileDefaults MapDefaults(InfisicalCertificateProfileDefaultsDto dto)
{
if (dto == null)
{
return null;
}
return new InfisicalCertificateProfileDefaults
{
TtlDays = dto.TtlDays,
KeyAlgorithm = dto.KeyAlgorithm,
SignatureAlgorithm = dto.SignatureAlgorithm,
KeyUsages = FlattenStringOrStringArray(dto.KeyUsagesRaw),
ExtendedKeyUsages = FlattenStringOrStringArray(dto.ExtendedKeyUsagesRaw)
};
}
private static InfisicalCertificateAuthoritySummary MapCa(InfisicalCertificateAuthoritySummaryDto dto)
{
if (dto == null)
{
return null;
}
return new InfisicalCertificateAuthoritySummary
{
Id = dto.Id,
Status = dto.Status,
Name = dto.Name,
IsExternal = dto.IsExternal,
ExternalType = dto.ExternalType
};
}
private static InfisicalCertificatePolicySummary MapPolicy(InfisicalCertificatePolicySummaryDto dto)
{
if (dto == null)
{
return null;
}
return new InfisicalCertificatePolicySummary
{
Id = dto.Id,
ProjectId = dto.ProjectId,
Name = dto.Name
};
}
private static InfisicalCertificateProfileApiConfig MapApiConfig(InfisicalCertificateProfileApiConfigDto dto)
{
if (dto == null)
{
return null;
}
return new InfisicalCertificateProfileApiConfig
{
Id = dto.Id,
AutoRenew = dto.AutoRenew,
RenewBeforeDays = dto.RenewBeforeDays
};
}
internal static string[] FlattenStringOrStringArray(JToken token)
{
if (token == null || token.Type == JTokenType.Null) { return null; }
if (token.Type == JTokenType.String) { return new[] { (string)token }; }
if (token.Type == JTokenType.Array)
{
List<string> items = new List<string>();
foreach (JToken child in (JArray)token)
{
if (child != null && child.Type == JTokenType.String) { items.Add((string)child); }
}
return items.ToArray();
}
return null;
}
private static DateTimeOffset? ParseTimestamp(string value)
{
if (string.IsNullOrEmpty(value)) { return null; }
DateTimeOffset parsed;
if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out parsed))
{
return parsed;
}
return null;
}
}
}
@@ -0,0 +1,341 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using PSInfisicalAPI.Logging;
using PSInfisicalAPI.Models;
namespace PSInfisicalAPI.Pki
{
internal static class InfisicalCertificateRequestHelpers
{
public static InfisicalCsrSubject MergeSubject(IDictionary subject, string commonName, string country, string state, string locality, string organization, string organizationalUnit, string emailAddress)
{
InfisicalCsrSubject result = new InfisicalCsrSubject();
if (subject != null)
{
result.CommonName = ReadString(subject, "CN", "CommonName");
result.Country = ReadString(subject, "C", "Country");
result.State = ReadString(subject, "ST", "S", "State");
result.Locality = ReadString(subject, "L", "Locality");
result.Organization = ReadString(subject, "O", "Organization");
result.OrganizationalUnit = ReadString(subject, "OU", "OrganizationalUnit");
result.EmailAddress = ReadString(subject, "E", "EMAIL", "EmailAddress");
}
if (!string.IsNullOrEmpty(commonName)) { result.CommonName = commonName; }
if (!string.IsNullOrEmpty(country)) { result.Country = country; }
if (!string.IsNullOrEmpty(state)) { result.State = state; }
if (!string.IsNullOrEmpty(locality)) { result.Locality = locality; }
if (!string.IsNullOrEmpty(organization)) { result.Organization = organization; }
if (!string.IsNullOrEmpty(organizationalUnit)) { result.OrganizationalUnit = organizationalUnit; }
if (!string.IsNullOrEmpty(emailAddress)) { result.EmailAddress = emailAddress; }
return result;
}
public static string ResolveLocalFqdn()
{
try
{
string host = System.Net.Dns.GetHostName();
string domain = null;
try { domain = System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties().DomainName; }
catch { domain = null; }
if (!string.IsNullOrEmpty(domain) && !host.EndsWith("." + domain, StringComparison.OrdinalIgnoreCase))
{
return string.Concat(host, ".", domain);
}
return host;
}
catch
{
return null;
}
}
public static void InstallToStore(X509Certificate2 cert, StoreName storeName, StoreLocation storeLocation, bool force, IInfisicalLogger logger, string component)
{
X509Store store = new X509Store(storeName, storeLocation);
try
{
store.Open(OpenFlags.ReadWrite);
X509Certificate2Collection existing = store.Certificates.Find(X509FindType.FindByThumbprint, cert.Thumbprint, false);
string target = string.Concat(storeLocation.ToString(), @"\", storeName.ToString(), " [", cert.Thumbprint, "]");
if (existing.Count > 0)
{
if (!force)
{
logger.Information(component, string.Concat("Certificate already present in ", target, "; no action taken."));
return;
}
store.RemoveRange(existing);
}
store.Add(cert);
logger.Information(component, string.Concat("Installed certificate to ", target, "."));
}
finally
{
store.Close();
}
}
public static void InstallChain(InfisicalSignedCertificate signed, StoreLocation storeLocation, bool force, IInfisicalLogger logger, string component)
{
List<X509Certificate2> chainCerts = CollectChainCertificates(signed);
InstallChain(chainCerts, storeLocation, force, logger, component);
}
public static void InstallChain(IEnumerable<X509Certificate2> chainCerts, StoreLocation storeLocation, bool force, IInfisicalLogger logger, string component)
{
if (chainCerts == null) { return; }
foreach (X509Certificate2 chainCert in chainCerts)
{
if (chainCert == null) { continue; }
StoreName targetStore = GetChainCertificateTargetStore(chainCert);
InstallToStore(chainCert, targetStore, storeLocation, force, logger, component);
}
}
public static StoreName GetChainCertificateTargetStore(X509Certificate2 cert)
{
return IsSelfSigned(cert) ? StoreName.Root : StoreName.CertificateAuthority;
}
public static X509KeyStorageFlags ResolveKeyStorageFlags(InfisicalPrivateKeyProtection protection, bool persistKey, bool machineKey)
{
X509KeyStorageFlags flags = X509KeyStorageFlags.DefaultKeySet;
switch (protection)
{
case InfisicalPrivateKeyProtection.Exportable:
flags |= X509KeyStorageFlags.Exportable;
break;
case InfisicalPrivateKeyProtection.Ephemeral:
const int ephemeralValue = 32;
if (Enum.GetName(typeof(X509KeyStorageFlags), ephemeralValue) == null)
{
throw new PlatformNotSupportedException("InfisicalPrivateKeyProtection.Ephemeral requires .NET Core 3.0 or later (PowerShell 7+). Use LocalOnly or NonExportable on Windows PowerShell 5.1.");
}
flags |= (X509KeyStorageFlags)ephemeralValue;
break;
}
if (machineKey) { flags |= X509KeyStorageFlags.MachineKeySet; }
if (persistKey) { flags |= X509KeyStorageFlags.PersistKeySet; }
return flags;
}
public static bool ShouldScrubPrivateKeyPem(InfisicalPrivateKeyProtection protection, bool hasExplicitPrivateKeyPath)
{
if (hasExplicitPrivateKeyPath) { return true; }
return protection == InfisicalPrivateKeyProtection.NonExportable
|| protection == InfisicalPrivateKeyProtection.Ephemeral;
}
public static void WritePrivateKeyPem(string privateKeyPem, string path)
{
if (string.IsNullOrEmpty(privateKeyPem)) { throw new ArgumentException("PrivateKeyPem is empty.", nameof(privateKeyPem)); }
if (string.IsNullOrEmpty(path)) { throw new ArgumentException("Path is required.", nameof(path)); }
string fullPath = System.IO.Path.GetFullPath(path);
string directory = System.IO.Path.GetDirectoryName(fullPath);
if (!string.IsNullOrEmpty(directory) && !System.IO.Directory.Exists(directory))
{
System.IO.Directory.CreateDirectory(directory);
}
System.IO.File.WriteAllText(fullPath, privateKeyPem);
}
public static InfisicalCertificateResult BuildResultFromExistingLocal(X509Certificate2 leaf)
{
return BuildResultFromExistingLocal(leaf, null);
}
public static InfisicalCertificateResult BuildResultFromExistingLocal(X509Certificate2 leaf, InfisicalCertificateBundle fallbackBundle)
{
if (leaf == null) { throw new ArgumentNullException(nameof(leaf)); }
InfisicalCertificateResult result = new InfisicalCertificateResult
{
Leaf = leaf,
SerialNumber = leaf.SerialNumber,
CertificatePem = ExportCertificateToPem(leaf)
};
List<X509Certificate2> chainElements = BuildLocalChain(leaf);
if (fallbackBundle != null && !string.IsNullOrEmpty(fallbackBundle.CertificateChainPem))
{
List<X509Certificate2> bundleChain = PemCertificateBuilder.ReadCertificateChain(fallbackBundle.CertificateChainPem);
HashSet<string> seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (X509Certificate2 c in chainElements) { if (c != null) { seen.Add(c.Thumbprint); } }
foreach (X509Certificate2 c in bundleChain)
{
if (c == null) { continue; }
if (seen.Add(c.Thumbprint)) { chainElements.Add(c); }
}
}
List<X509Certificate2> intermediates = new List<X509Certificate2>();
X509Certificate2 root = null;
foreach (X509Certificate2 cert in chainElements)
{
if (string.Equals(cert.Thumbprint, leaf.Thumbprint, StringComparison.OrdinalIgnoreCase)) { continue; }
if (IsSelfSigned(cert)) { if (root == null) { root = cert; } }
else { intermediates.Add(cert); }
}
result.Intermediates = intermediates.ToArray();
result.Root = root;
List<X509Certificate2> ordered = new List<X509Certificate2> { leaf };
ordered.AddRange(intermediates);
if (root != null) { ordered.Add(root); }
result.Chain = ordered.ToArray();
if (intermediates.Count > 0 || root != null)
{
StringBuilder sb = new StringBuilder();
foreach (X509Certificate2 c in intermediates) { sb.Append(ExportCertificateToPem(c)); }
if (root != null) { sb.Append(ExportCertificateToPem(root)); }
result.CertificateChainPem = sb.ToString();
}
return result;
}
public static InfisicalCertificateResult BuildResult(X509Certificate2 leaf, InfisicalSignedCertificate signed)
{
InfisicalCertificateResult result = new InfisicalCertificateResult { Leaf = leaf };
if (signed != null)
{
result.SerialNumber = signed.SerialNumber;
result.CertificatePem = signed.CertificatePem;
result.CertificateChainPem = signed.CertificateChainPem;
result.PrivateKeyPem = signed.PrivateKeyPem;
result.Status = signed.Status;
result.StatusMessage = signed.StatusMessage;
result.CertificateRequestId = signed.CertificateRequestId;
}
List<X509Certificate2> chainCerts = signed != null ? CollectChainCertificates(signed) : new List<X509Certificate2>();
List<X509Certificate2> intermediates = new List<X509Certificate2>();
X509Certificate2 root = null;
foreach (X509Certificate2 cert in chainCerts)
{
if (IsSelfSigned(cert)) { if (root == null) { root = cert; } }
else { intermediates.Add(cert); }
}
result.Intermediates = intermediates.ToArray();
result.Root = root;
List<X509Certificate2> ordered = new List<X509Certificate2>();
if (leaf != null) { ordered.Add(leaf); }
ordered.AddRange(intermediates);
if (root != null) { ordered.Add(root); }
result.Chain = ordered.ToArray();
return result;
}
private static List<X509Certificate2> CollectChainCertificates(InfisicalSignedCertificate signed)
{
List<X509Certificate2> chainCerts = PemCertificateBuilder.ReadCertificateChain(signed.CertificateChainPem);
if (!string.IsNullOrEmpty(signed.IssuingCaCertificatePem))
{
foreach (X509Certificate2 issuing in PemCertificateBuilder.ReadCertificateChain(signed.IssuingCaCertificatePem))
{
chainCerts.Add(issuing);
}
}
HashSet<string> seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
List<X509Certificate2> deduped = new List<X509Certificate2>();
foreach (X509Certificate2 cert in chainCerts)
{
if (cert == null) { continue; }
if (seen.Add(cert.Thumbprint)) { deduped.Add(cert); }
}
return deduped;
}
private static bool IsSelfSigned(X509Certificate2 cert)
{
if (cert == null) { return false; }
return string.Equals(cert.Subject, cert.Issuer, StringComparison.OrdinalIgnoreCase);
}
private static List<X509Certificate2> BuildLocalChain(X509Certificate2 leaf)
{
List<X509Certificate2> result = new List<X509Certificate2>();
using (X509Chain chain = new X509Chain())
{
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
chain.ChainPolicy.VerificationFlags =
X509VerificationFlags.IgnoreNotTimeValid |
X509VerificationFlags.IgnoreNotTimeNested |
X509VerificationFlags.IgnoreInvalidName |
X509VerificationFlags.IgnoreInvalidPolicy |
X509VerificationFlags.IgnoreEndRevocationUnknown |
X509VerificationFlags.IgnoreCertificateAuthorityRevocationUnknown |
X509VerificationFlags.IgnoreRootRevocationUnknown |
X509VerificationFlags.IgnoreCtlNotTimeValid |
X509VerificationFlags.IgnoreCtlSignerRevocationUnknown |
X509VerificationFlags.IgnoreInvalidBasicConstraints |
X509VerificationFlags.IgnoreWrongUsage;
try { chain.Build(leaf); }
catch { return result; }
foreach (X509ChainElement element in chain.ChainElements)
{
if (element != null && element.Certificate != null)
{
result.Add(new X509Certificate2(element.Certificate.RawData));
}
}
}
return result;
}
private static string ExportCertificateToPem(X509Certificate2 cert)
{
byte[] der = cert.Export(X509ContentType.Cert);
StringBuilder sb = new StringBuilder();
sb.AppendLine("-----BEGIN CERTIFICATE-----");
sb.AppendLine(Convert.ToBase64String(der, Base64FormattingOptions.InsertLineBreaks));
sb.AppendLine("-----END CERTIFICATE-----");
return sb.ToString();
}
private static string ReadString(IDictionary source, params string[] keys)
{
foreach (string key in keys)
{
if (source.Contains(key))
{
object value = source[key];
if (value != null)
{
string text = value.ToString();
if (!string.IsNullOrEmpty(text))
{
return text;
}
}
}
}
return null;
}
}
}
@@ -0,0 +1,201 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Pkcs;
using Org.BouncyCastle.Asn1.Sec;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;
using BcAttribute = Org.BouncyCastle.Asn1.Cms.Attribute;
namespace PSInfisicalAPI.Pki
{
public enum InfisicalKeyAlgorithm
{
Rsa = 0,
Ecdsa = 1,
Ed25519 = 2
}
public enum InfisicalEcCurve
{
P256 = 0,
P384 = 1
}
public sealed class InfisicalCsrSubject
{
public string CommonName { get; set; }
public string Country { get; set; }
public string State { get; set; }
public string Locality { get; set; }
public string Organization { get; set; }
public string OrganizationalUnit { get; set; }
public string EmailAddress { get; set; }
}
public sealed class InfisicalCsrOptions
{
public InfisicalKeyAlgorithm KeyAlgorithm { get; set; } = InfisicalKeyAlgorithm.Rsa;
public int RsaKeySize { get; set; } = 2048;
public InfisicalEcCurve EcCurve { get; set; } = InfisicalEcCurve.P256;
}
public sealed class InfisicalCsrResult
{
public string CsrPem { get; set; }
public string PrivateKeyPem { get; set; }
}
public static class InfisicalCsrBuilder
{
public static InfisicalCsrResult Build(InfisicalCsrSubject subject, IEnumerable<string> dnsNames, IEnumerable<string> ipAddresses, InfisicalCsrOptions options)
{
if (subject == null) { throw new ArgumentNullException(nameof(subject)); }
if (string.IsNullOrEmpty(subject.CommonName)) { throw new ArgumentException("Subject.CommonName is required.", nameof(subject)); }
if (options == null) { options = new InfisicalCsrOptions(); }
SecureRandom random = new SecureRandom();
AsymmetricCipherKeyPair keyPair = GenerateKeyPair(options, random);
string signatureAlgorithm = ResolveSignatureAlgorithm(options);
X509Name x509Name = BuildX509Name(subject);
Asn1Set attributes = BuildSanAttributes(dnsNames, ipAddresses);
Pkcs10CertificationRequest pkcs10 = new Pkcs10CertificationRequest(signatureAlgorithm, x509Name, keyPair.Public, attributes, keyPair.Private);
return new InfisicalCsrResult
{
CsrPem = WritePem(pkcs10),
PrivateKeyPem = WritePem(keyPair.Private)
};
}
private static AsymmetricCipherKeyPair GenerateKeyPair(InfisicalCsrOptions options, SecureRandom random)
{
switch (options.KeyAlgorithm)
{
case InfisicalKeyAlgorithm.Rsa:
{
int keySize = options.RsaKeySize;
if (keySize != 2048 && keySize != 3072 && keySize != 4096)
{
throw new ArgumentException("RsaKeySize must be 2048, 3072, or 4096.", nameof(options));
}
RsaKeyPairGenerator generator = new RsaKeyPairGenerator();
generator.Init(new KeyGenerationParameters(random, keySize));
return generator.GenerateKeyPair();
}
case InfisicalKeyAlgorithm.Ecdsa:
{
DerObjectIdentifier curveOid = options.EcCurve == InfisicalEcCurve.P384
? SecObjectIdentifiers.SecP384r1
: SecObjectIdentifiers.SecP256r1;
ECKeyPairGenerator generator = new ECKeyPairGenerator("ECDSA");
generator.Init(new ECKeyGenerationParameters(curveOid, random));
return generator.GenerateKeyPair();
}
case InfisicalKeyAlgorithm.Ed25519:
{
Ed25519KeyPairGenerator generator = new Ed25519KeyPairGenerator();
generator.Init(new Ed25519KeyGenerationParameters(random));
return generator.GenerateKeyPair();
}
default:
throw new ArgumentOutOfRangeException(nameof(options), options.KeyAlgorithm, "Unsupported KeyAlgorithm.");
}
}
private static string ResolveSignatureAlgorithm(InfisicalCsrOptions options)
{
switch (options.KeyAlgorithm)
{
case InfisicalKeyAlgorithm.Rsa:
return "SHA256WITHRSA";
case InfisicalKeyAlgorithm.Ecdsa:
return options.EcCurve == InfisicalEcCurve.P384 ? "SHA384WITHECDSA" : "SHA256WITHECDSA";
case InfisicalKeyAlgorithm.Ed25519:
return "Ed25519";
default:
throw new ArgumentOutOfRangeException(nameof(options), options.KeyAlgorithm, "Unsupported KeyAlgorithm.");
}
}
private static X509Name BuildX509Name(InfisicalCsrSubject subject)
{
List<DerObjectIdentifier> order = new List<DerObjectIdentifier>();
Dictionary<DerObjectIdentifier, string> values = new Dictionary<DerObjectIdentifier, string>();
AppendComponent(order, values, X509Name.C, subject.Country);
AppendComponent(order, values, X509Name.ST, subject.State);
AppendComponent(order, values, X509Name.L, subject.Locality);
AppendComponent(order, values, X509Name.O, subject.Organization);
AppendComponent(order, values, X509Name.OU, subject.OrganizationalUnit);
AppendComponent(order, values, X509Name.CN, subject.CommonName);
AppendComponent(order, values, X509Name.EmailAddress, subject.EmailAddress);
return new X509Name(order, values);
}
private static void AppendComponent(List<DerObjectIdentifier> order, Dictionary<DerObjectIdentifier, string> values, DerObjectIdentifier oid, string value)
{
if (string.IsNullOrEmpty(value)) { return; }
order.Add(oid);
values[oid] = value;
}
private static Asn1Set BuildSanAttributes(IEnumerable<string> dnsNames, IEnumerable<string> ipAddresses)
{
List<GeneralName> generalNames = new List<GeneralName>();
if (dnsNames != null)
{
foreach (string dns in dnsNames)
{
if (string.IsNullOrEmpty(dns)) { continue; }
generalNames.Add(new GeneralName(GeneralName.DnsName, dns));
}
}
if (ipAddresses != null)
{
foreach (string ip in ipAddresses)
{
if (string.IsNullOrEmpty(ip)) { continue; }
IPAddress parsed;
if (!IPAddress.TryParse(ip, out parsed)) { continue; }
generalNames.Add(new GeneralName(GeneralName.IPAddress, ip));
}
}
if (generalNames.Count == 0) { return null; }
GeneralNames sanValue = new GeneralNames(generalNames.ToArray());
X509Extensions extensions = new X509Extensions(
new Dictionary<DerObjectIdentifier, X509Extension>
{
{ X509Extensions.SubjectAlternativeName, new X509Extension(false, new DerOctetString(sanValue)) }
});
BcAttribute extensionRequest = new BcAttribute(PkcsObjectIdentifiers.Pkcs9AtExtensionRequest, new DerSet(extensions));
return new DerSet(extensionRequest);
}
private static string WritePem(object obj)
{
using (StringWriter sw = new StringWriter())
{
PemWriter pemWriter = new PemWriter(sw);
pemWriter.WriteObject(obj);
pemWriter.Writer.Flush();
return sw.ToString();
}
}
}
}
@@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using System.Security.Cryptography.X509Certificates;
namespace PSInfisicalAPI.Pki
{
internal static class InfisicalLocalCertificateLookup
{
public static X509Certificate2 FindMatch(StoreName storeName, StoreLocation storeLocation, string commonName, IEnumerable<string> candidateSerialNumbers)
{
HashSet<string> serialSet = NormalizeSerials(candidateSerialNumbers);
string subjectFilter = !string.IsNullOrEmpty(commonName) ? string.Concat("CN=", commonName) : null;
X509Store store = new X509Store(storeName, storeLocation);
try
{
store.Open(OpenFlags.ReadOnly);
X509Certificate2 bestMatch = null;
foreach (X509Certificate2 candidate in store.Certificates)
{
if (subjectFilter != null && candidate.Subject.IndexOf(subjectFilter, StringComparison.OrdinalIgnoreCase) < 0)
{
continue;
}
if (serialSet.Count > 0)
{
string normalizedSerial = NormalizeSerial(candidate.SerialNumber);
if (!serialSet.Contains(normalizedSerial))
{
continue;
}
}
if (bestMatch == null || candidate.NotAfter > bestMatch.NotAfter)
{
bestMatch = candidate;
}
}
return bestMatch;
}
finally
{
store.Close();
}
}
public static bool IsRenewable(X509Certificate2 cert, int renewalThresholdDays)
{
if (cert == null) { return true; }
DateTime threshold = DateTime.UtcNow.AddDays(renewalThresholdDays);
return cert.NotAfter.ToUniversalTime() <= threshold;
}
private static HashSet<string> NormalizeSerials(IEnumerable<string> serials)
{
HashSet<string> set = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
if (serials == null) { return set; }
foreach (string serial in serials)
{
string normalized = NormalizeSerial(serial);
if (!string.IsNullOrEmpty(normalized))
{
set.Add(normalized);
}
}
return set;
}
private static string NormalizeSerial(string value)
{
if (string.IsNullOrEmpty(value)) { return null; }
string trimmed = value.Trim();
if (trimmed.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
{
trimmed = trimmed.Substring(2);
}
return trimmed.Replace(":", string.Empty).Replace(" ", string.Empty).TrimStart('0');
}
}
}
+727 -19
View File
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using Newtonsoft.Json.Linq;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Endpoints;
using PSInfisicalAPI.Errors;
@@ -8,6 +9,7 @@ using PSInfisicalAPI.Http;
using PSInfisicalAPI.Logging;
using PSInfisicalAPI.Models;
using PSInfisicalAPI.Serialization;
using System.Linq;
namespace PSInfisicalAPI.Pki
{
@@ -30,23 +32,22 @@ namespace PSInfisicalAPI.Pki
public InfisicalCertificateAuthority[] ListInternalCertificateAuthorities(InfisicalConnection connection, string projectId)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
List<KeyValuePair<string, string>> query = null;
if (!string.IsNullOrEmpty(resolvedProjectId))
if (!string.IsNullOrEmpty(projectId))
{
query = new List<KeyValuePair<string, string>> { new KeyValuePair<string, string>("projectId", resolvedProjectId) };
query = new List<KeyValuePair<string, string>> { new KeyValuePair<string, string>("projectId", projectId) };
}
try
{
_logger.Information(Component, "Attempting to list Infisical internal certificate authorities. Please Wait...");
InfisicalHttpResponse response = _invoker.InvokeWithCandidateFallback(connection, InfisicalEndpointNames.ListInternalCertificateAuthorities, "ListInternalCertificateAuthorities", null, query, null);
InfisicalInternalCaListResponseDto dto = _serializer.Deserialize<InfisicalInternalCaListResponseDto>(response.Body);
string body = response.Body;
response.Clear();
List<InfisicalInternalCaResponseDto> source = dto != null ? (dto.CertificateAuthorities ?? dto.Cas) : null;
InfisicalCertificateAuthority[] mapped = InfisicalCaMapper.MapMany(source, resolvedProjectId);
List<InfisicalInternalCaResponseDto> source = ParseCaListBody(body);
InfisicalCertificateAuthority[] mapped = InfisicalCaMapper.MapMany(source, projectId);
_logger.Information(Component, "Infisical internal certificate authority list retrieval was successful.");
return mapped;
}
@@ -63,21 +64,21 @@ namespace PSInfisicalAPI.Pki
if (string.IsNullOrEmpty(caId)) { throw new InfisicalConfigurationException("CaId is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "caId", caId } };
List<KeyValuePair<string, string>> query = null;
if (!string.IsNullOrEmpty(projectId))
{
query = new List<KeyValuePair<string, string>> { new KeyValuePair<string, string>("projectId", projectId) };
}
try
{
_logger.Information(Component, string.Concat("Attempting to retrieve Infisical internal certificate authority '", caId, "'. Please Wait..."));
InfisicalHttpResponse response = _invoker.InvokeWithCandidateFallback(connection, InfisicalEndpointNames.RetrieveInternalCertificateAuthority, "RetrieveInternalCertificateAuthority", pathParameters, null, null);
InfisicalInternalCaSingleResponseDto dto = _serializer.Deserialize<InfisicalInternalCaSingleResponseDto>(response.Body);
InfisicalHttpResponse response = _invoker.InvokeWithCandidateFallback(connection, InfisicalEndpointNames.RetrieveInternalCertificateAuthority, "RetrieveInternalCertificateAuthority", pathParameters, query, null);
string body = response.Body;
response.Clear();
InfisicalInternalCaResponseDto inner = dto != null ? (dto.CertificateAuthority ?? dto.Ca) : null;
if (inner == null)
{
inner = _serializer.Deserialize<InfisicalInternalCaResponseDto>(response.Body);
}
InfisicalCertificateAuthority mapped = InfisicalCaMapper.Map(inner, FirstNonEmpty(projectId, connection.ProjectId));
InfisicalInternalCaResponseDto inner = ParseCaSingleBody(body);
InfisicalCertificateAuthority mapped = InfisicalCaMapper.Map(inner, projectId);
_logger.Information(Component, "Infisical internal certificate authority retrieval was successful.");
return mapped;
}
@@ -88,14 +89,104 @@ namespace PSInfisicalAPI.Pki
}
}
public InfisicalCertificateAuthority[] ListAllCertificateAuthorities(InfisicalConnection connection, string projectId)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
if (string.IsNullOrEmpty(projectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
List<KeyValuePair<string, string>> query = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("projectId", projectId)
};
try
{
_logger.Information(Component, "Attempting to list Infisical certificate authorities. Please Wait...");
InfisicalHttpResponse response = _invoker.InvokeWithCandidateFallback(connection, InfisicalEndpointNames.ListCertificateAuthorities, "ListCertificateAuthorities", null, query, null);
string body = response.Body;
response.Clear();
List<InfisicalInternalCaResponseDto> source = ParseCaListBody(body);
InfisicalCertificateAuthority[] mapped = InfisicalCaMapper.MapMany(source, projectId);
_logger.Information(Component, "Infisical certificate authority list retrieval was successful.");
return mapped;
}
catch (Exception)
{
_logger.Error(Component, "Infisical certificate authority list retrieval failed.");
throw;
}
}
public InfisicalCertificate RetrieveCertificate(InfisicalConnection connection, string identifier)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
if (string.IsNullOrEmpty(identifier)) { throw new InfisicalConfigurationException("Identifier (serial number or id) is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "serialNumber", identifier } };
try
{
_logger.Information(Component, string.Concat("Attempting to retrieve Infisical certificate '", identifier, "'. Please Wait..."));
InfisicalHttpResponse response = _invoker.InvokeWithCandidateFallback(connection, InfisicalEndpointNames.RetrieveCertificate, "RetrieveCertificate", pathParameters, null, null);
string body = response.Body;
response.Clear();
InfisicalCertificateResponseDto inner = ParseCertificateSingleBody(body);
InfisicalCertificate mapped = InfisicalCertificateMapper.Map(inner, null);
_logger.Information(Component, "Infisical certificate retrieval was successful.");
return mapped;
}
catch (Exception)
{
_logger.Error(Component, "Infisical certificate retrieval failed.");
throw;
}
}
private List<InfisicalInternalCaResponseDto> ParseCaListBody(string body)
{
if (string.IsNullOrEmpty(body)) { return null; }
JToken token = JToken.Parse(body);
if (token.Type == JTokenType.Array)
{
return token.ToObject<List<InfisicalInternalCaResponseDto>>();
}
InfisicalInternalCaListResponseDto wrapper = token.ToObject<InfisicalInternalCaListResponseDto>();
return wrapper != null ? (wrapper.CertificateAuthorities ?? wrapper.Cas) : null;
}
private InfisicalInternalCaResponseDto ParseCaSingleBody(string body)
{
if (string.IsNullOrEmpty(body)) { return null; }
JToken token = JToken.Parse(body);
if (token.Type != JTokenType.Object) { return null; }
JObject obj = (JObject)token;
if (obj["certificateAuthority"] is JObject ca1) { return ca1.ToObject<InfisicalInternalCaResponseDto>(); }
if (obj["ca"] is JObject ca2) { return ca2.ToObject<InfisicalInternalCaResponseDto>(); }
return obj.ToObject<InfisicalInternalCaResponseDto>();
}
private InfisicalCertificateResponseDto ParseCertificateSingleBody(string body)
{
if (string.IsNullOrEmpty(body)) { return null; }
JToken token = JToken.Parse(body);
if (token.Type != JTokenType.Object) { return null; }
JObject obj = (JObject)token;
if (obj["certificate"] is JObject cert) { return cert.ToObject<InfisicalCertificateResponseDto>(); }
return obj.ToObject<InfisicalCertificateResponseDto>();
}
public InfisicalCertificateSearchResult SearchCertificates(InfisicalConnection connection, InfisicalCertificateSearchQuery query)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
if (query == null) { throw new ArgumentNullException(nameof(query)); }
string resolvedProjectId = FirstNonEmpty(query.ProjectId, connection.ProjectId);
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(query.ProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", resolvedProjectId } };
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", query.ProjectId } };
InfisicalCertificateSearchRequestDto request = BuildSearchRequest(query);
string body = _serializer.Serialize(request);
@@ -106,7 +197,7 @@ namespace PSInfisicalAPI.Pki
InfisicalCertificateSearchResponseDto dto = _serializer.Deserialize<InfisicalCertificateSearchResponseDto>(response.Body);
response.Clear();
InfisicalCertificate[] mapped = InfisicalCertificateMapper.MapMany(dto != null ? dto.Certificates : null, resolvedProjectId);
InfisicalCertificate[] mapped = InfisicalCertificateMapper.MapMany(dto != null ? dto.Certificates : null, query.ProjectId);
int total = dto != null ? dto.TotalCount : mapped.Length;
_logger.Information(Component, "Infisical certificate search was successful.");
return new InfisicalCertificateSearchResult { Certificates = mapped, TotalCount = total };
@@ -118,6 +209,401 @@ namespace PSInfisicalAPI.Pki
}
}
public InfisicalSignedCertificate SignCertificateBySubscriber(InfisicalConnection connection, string subscriberName, string projectId, string csrPem)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
if (string.IsNullOrEmpty(subscriberName)) { throw new InfisicalConfigurationException("SubscriberName is required."); }
if (string.IsNullOrEmpty(csrPem)) { throw new InfisicalConfigurationException("CSR is required."); }
if (string.IsNullOrEmpty(projectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "subscriberName", subscriberName } };
InfisicalSignCertificateBySubscriberRequestDto request = new InfisicalSignCertificateBySubscriberRequestDto
{
ProjectId = projectId,
Csr = csrPem
};
string body = _serializer.Serialize(request);
try
{
_logger.Information(Component, string.Concat("Attempting to sign certificate via subscriber '", subscriberName, "'. Please Wait..."));
InfisicalHttpResponse response = _invoker.InvokeWithCandidateFallback(connection, InfisicalEndpointNames.SignCertificateBySubscriber, "SignCertificateBySubscriber", pathParameters, null, body);
InfisicalSignCertificateResponseDto dto = _serializer.Deserialize<InfisicalSignCertificateResponseDto>(response.Body);
response.Clear();
InfisicalSignedCertificate signed = MapSigned(dto);
_logger.Information(Component, "Infisical certificate signing (subscriber) was successful.");
return signed;
}
catch (Exception)
{
_logger.Error(Component, "Infisical certificate signing (subscriber) failed.");
throw;
}
}
public InfisicalSignedCertificate SignCertificateByCa(InfisicalConnection connection, string caId, string csrPem, string commonName, string altNames, string ttl, string notBefore, string notAfter, string friendlyName, string pkiCollectionId, IEnumerable<string> keyUsages, IEnumerable<string> extendedKeyUsages)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
if (string.IsNullOrEmpty(caId)) { throw new InfisicalConfigurationException("CaId is required."); }
if (string.IsNullOrEmpty(csrPem)) { throw new InfisicalConfigurationException("CSR is required."); }
if (string.IsNullOrEmpty(ttl) && string.IsNullOrEmpty(notAfter)) { throw new InfisicalConfigurationException("Either Ttl or NotAfter must be provided."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "caId", caId } };
InfisicalSignCertificateByCaRequestDto request = new InfisicalSignCertificateByCaRequestDto
{
Csr = csrPem,
CommonName = commonName,
AltNames = altNames,
Ttl = ttl,
NotBefore = notBefore,
NotAfter = notAfter,
FriendlyName = friendlyName,
PkiCollectionId = pkiCollectionId,
KeyUsages = keyUsages != null ? keyUsages.ToList() : null,
ExtendedKeyUsages = extendedKeyUsages != null ? extendedKeyUsages.ToList() : null
};
string body = _serializer.Serialize(request);
try
{
_logger.Information(Component, string.Concat("Attempting to sign certificate via CA '", caId, "'. Please Wait..."));
InfisicalHttpResponse response = _invoker.InvokeWithCandidateFallback(connection, InfisicalEndpointNames.SignCertificateByCa, "SignCertificateByCa", pathParameters, null, body);
InfisicalSignCertificateResponseDto dto = _serializer.Deserialize<InfisicalSignCertificateResponseDto>(response.Body);
response.Clear();
InfisicalSignedCertificate signed = MapSigned(dto);
_logger.Information(Component, "Infisical certificate signing (CA) was successful.");
return signed;
}
catch (Exception)
{
_logger.Error(Component, "Infisical certificate signing (CA) failed.");
throw;
}
}
private static InfisicalSignedCertificate MapSigned(InfisicalSignCertificateResponseDto dto)
{
if (dto == null) { return null; }
return new InfisicalSignedCertificate
{
SerialNumber = dto.SerialNumber,
CertificatePem = dto.Certificate,
CertificateChainPem = dto.CertificateChain,
IssuingCaCertificatePem = dto.IssuingCaCertificate
};
}
public InfisicalSignedCertificate IssueCertificateByProfile(InfisicalConnection connection, string profileId, string csrPem, string commonName, string organization, string organizationalUnit, string country, string state, string locality, string ttl, string notBefore, string notAfter, IEnumerable<string> keyUsages, IEnumerable<string> extendedKeyUsages)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
if (string.IsNullOrEmpty(profileId)) { throw new InfisicalConfigurationException("CertificateProfileId is required."); }
if (string.IsNullOrEmpty(csrPem)) { throw new InfisicalConfigurationException("CSR is required."); }
InfisicalIssueCertificateAttributesDto attributes = new InfisicalIssueCertificateAttributesDto
{
CommonName = commonName,
Organization = organization,
OrganizationalUnit = organizationalUnit,
Country = country,
State = state,
Locality = locality,
Ttl = ttl,
NotBefore = notBefore,
NotAfter = notAfter,
KeyUsages = keyUsages != null ? new List<string>(keyUsages) : null,
ExtendedKeyUsages = extendedKeyUsages != null ? new List<string>(extendedKeyUsages) : null
};
InfisicalIssueCertificateByProfileRequestDto request = new InfisicalIssueCertificateByProfileRequestDto
{
ProfileId = profileId,
Csr = csrPem,
Attributes = attributes
};
string body = _serializer.Serialize(request);
try
{
_logger.Information(Component, string.Concat("Attempting to issue certificate via profile '", profileId, "'. Please Wait..."));
InfisicalHttpResponse response = _invoker.InvokeWithCandidateFallback(connection, InfisicalEndpointNames.IssueCertificateByProfile, "IssueCertificateByProfile", null, null, body);
InfisicalIssueCertificateResponseDto dto = _serializer.Deserialize<InfisicalIssueCertificateResponseDto>(response.Body);
response.Clear();
if (dto == null || dto.Certificate == null || string.IsNullOrEmpty(dto.Certificate.Certificate))
{
string status = dto != null ? dto.Status : "unknown";
string message = dto != null ? dto.Message : null;
string requestId = dto != null ? dto.CertificateRequestId : null;
_logger.Warning(Component, string.Concat("Profile issuance did not return a certificate (status='", status ?? "unknown", "'", string.IsNullOrEmpty(message) ? "" : string.Concat(", message='", message, "'"), string.IsNullOrEmpty(requestId) ? "" : string.Concat(", certificateRequestId='", requestId, "'"), "). The profile may require manual approval or additional validation; returning a status-only result."));
return new InfisicalSignedCertificate
{
Status = status,
StatusMessage = message,
CertificateRequestId = requestId
};
}
InfisicalSignedCertificate signed = new InfisicalSignedCertificate
{
SerialNumber = dto.Certificate.SerialNumber,
CertificatePem = dto.Certificate.Certificate,
CertificateChainPem = dto.Certificate.CertificateChain,
IssuingCaCertificatePem = dto.Certificate.IssuingCaCertificate,
Status = dto.Status,
StatusMessage = dto.Message,
CertificateRequestId = dto.CertificateRequestId
};
_logger.Information(Component, "Infisical certificate issuance (profile) was successful.");
return signed;
}
catch (Exception)
{
_logger.Error(Component, "Infisical certificate issuance (profile) failed.");
throw;
}
}
public InfisicalPkiSubscriber[] ListPkiSubscribers(InfisicalConnection connection, string projectId)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
if (string.IsNullOrEmpty(projectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", projectId } };
try
{
_logger.Information(Component, "Attempting to list Infisical PKI subscribers. Please Wait...");
InfisicalHttpResponse response = _invoker.InvokeWithCandidateFallback(connection, InfisicalEndpointNames.ListPkiSubscribers, "ListPkiSubscribers", pathParameters, null, null);
string body = response.Body;
response.Clear();
List<InfisicalPkiSubscriberResponseDto> source = ParsePkiSubscriberListBody(body);
InfisicalPkiSubscriber[] mapped = InfisicalPkiSubscriberMapper.MapMany(source, projectId);
_logger.Information(Component, "Infisical PKI subscriber list retrieval was successful.");
return mapped;
}
catch (Exception)
{
_logger.Error(Component, "Infisical PKI subscriber list retrieval failed.");
throw;
}
}
public InfisicalPkiSubscriber GetPkiSubscriber(InfisicalConnection connection, string subscriberName, string projectId)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
if (string.IsNullOrEmpty(subscriberName)) { throw new InfisicalConfigurationException("SubscriberName is required."); }
if (string.IsNullOrEmpty(projectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "subscriberName", subscriberName } };
List<KeyValuePair<string, string>> query = new List<KeyValuePair<string, string>> { new KeyValuePair<string, string>("projectId", projectId) };
try
{
_logger.Information(Component, string.Concat("Attempting to retrieve Infisical PKI subscriber '", subscriberName, "'. Please Wait..."));
InfisicalHttpResponse response = _invoker.InvokeWithCandidateFallback(connection, InfisicalEndpointNames.GetPkiSubscriber, "GetPkiSubscriber", pathParameters, query, null);
InfisicalPkiSubscriberResponseDto dto = _serializer.Deserialize<InfisicalPkiSubscriberResponseDto>(response.Body);
response.Clear();
InfisicalPkiSubscriber mapped = InfisicalPkiSubscriberMapper.Map(dto, projectId);
_logger.Information(Component, "Infisical PKI subscriber retrieval was successful.");
return mapped;
}
catch (Exception)
{
_logger.Error(Component, "Infisical PKI subscriber retrieval failed.");
throw;
}
}
private List<InfisicalPkiSubscriberResponseDto> ParsePkiSubscriberListBody(string body)
{
if (string.IsNullOrEmpty(body)) { return null; }
JToken token = JToken.Parse(body);
if (token.Type == JTokenType.Array)
{
return token.ToObject<List<InfisicalPkiSubscriberResponseDto>>();
}
InfisicalPkiSubscriberListResponseDto wrapper = token.ToObject<InfisicalPkiSubscriberListResponseDto>();
return wrapper != null ? wrapper.Subscribers : null;
}
public InfisicalCertificateProfile[] ListCertificateProfiles(InfisicalConnection connection, string projectId, int? limit, int? offset, bool? includeConfigs)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
if (string.IsNullOrEmpty(projectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
List<KeyValuePair<string, string>> query = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("projectId", projectId)
};
if (limit.HasValue) { query.Add(new KeyValuePair<string, string>("limit", limit.Value.ToString(CultureInfo.InvariantCulture))); }
if (offset.HasValue) { query.Add(new KeyValuePair<string, string>("offset", offset.Value.ToString(CultureInfo.InvariantCulture))); }
if (includeConfigs.HasValue) { query.Add(new KeyValuePair<string, string>("includeConfigs", includeConfigs.Value ? "true" : "false")); }
try
{
_logger.Information(Component, "Attempting to list Infisical certificate profiles. Please Wait...");
InfisicalHttpResponse response = _invoker.InvokeWithCandidateFallback(connection, InfisicalEndpointNames.ListCertificateProfiles, "ListCertificateProfiles", null, query, null);
string body = response.Body;
response.Clear();
List<InfisicalCertificateProfileResponseDto> source = ParseCertificateProfileListBody(body);
InfisicalCertificateProfile[] mapped = InfisicalCertificateProfileMapper.MapMany(source, projectId);
_logger.Information(Component, "Infisical certificate profile list retrieval was successful.");
return mapped;
}
catch (Exception)
{
_logger.Error(Component, "Infisical certificate profile list retrieval failed.");
throw;
}
}
public InfisicalCertificateProfile GetCertificateProfile(InfisicalConnection connection, string certificateProfileId, string projectId)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
if (string.IsNullOrEmpty(certificateProfileId)) { throw new InfisicalConfigurationException("CertificateProfileId is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "certificateProfileId", certificateProfileId } };
List<KeyValuePair<string, string>> query = null;
if (!string.IsNullOrEmpty(projectId))
{
query = new List<KeyValuePair<string, string>> { new KeyValuePair<string, string>("projectId", projectId) };
}
try
{
_logger.Information(Component, string.Concat("Attempting to retrieve Infisical certificate profile '", certificateProfileId, "'. Please Wait..."));
InfisicalHttpResponse response = _invoker.InvokeWithCandidateFallback(connection, InfisicalEndpointNames.GetCertificateProfile, "GetCertificateProfile", pathParameters, query, null);
string body = response.Body;
response.Clear();
InfisicalCertificateProfileResponseDto inner = ParseCertificateProfileSingleBody(body);
InfisicalCertificateProfile mapped = InfisicalCertificateProfileMapper.Map(inner, projectId);
_logger.Information(Component, "Infisical certificate profile retrieval was successful.");
return mapped;
}
catch (Exception)
{
_logger.Error(Component, "Infisical certificate profile retrieval failed.");
throw;
}
}
private List<InfisicalCertificateProfileResponseDto> ParseCertificateProfileListBody(string body)
{
if (string.IsNullOrEmpty(body)) { return null; }
JToken token = JToken.Parse(body);
if (token.Type == JTokenType.Array)
{
return token.ToObject<List<InfisicalCertificateProfileResponseDto>>();
}
InfisicalCertificateProfileListResponseDto wrapper = token.ToObject<InfisicalCertificateProfileListResponseDto>();
return wrapper != null ? wrapper.CertificateProfiles : null;
}
private InfisicalCertificateProfileResponseDto ParseCertificateProfileSingleBody(string body)
{
if (string.IsNullOrEmpty(body)) { return null; }
JToken token = JToken.Parse(body);
if (token.Type != JTokenType.Object) { return null; }
JObject obj = (JObject)token;
if (obj["certificateProfile"] is JObject inner) { return inner.ToObject<InfisicalCertificateProfileResponseDto>(); }
return obj.ToObject<InfisicalCertificateProfileResponseDto>();
}
public InfisicalCertificatePolicy[] ListCertificatePolicies(InfisicalConnection connection, string projectId, int? limit, int? offset)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
if (string.IsNullOrEmpty(projectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
List<KeyValuePair<string, string>> query = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("projectId", projectId)
};
if (limit.HasValue) { query.Add(new KeyValuePair<string, string>("limit", limit.Value.ToString(CultureInfo.InvariantCulture))); }
if (offset.HasValue) { query.Add(new KeyValuePair<string, string>("offset", offset.Value.ToString(CultureInfo.InvariantCulture))); }
try
{
_logger.Information(Component, "Attempting to list Infisical certificate policies. Please Wait...");
InfisicalHttpResponse response = _invoker.InvokeWithCandidateFallback(connection, InfisicalEndpointNames.ListCertificatePolicies, "ListCertificatePolicies", null, query, null);
string body = response.Body;
response.Clear();
List<InfisicalCertificatePolicyResponseDto> source = ParseCertificatePolicyListBody(body);
InfisicalCertificatePolicy[] mapped = InfisicalCertificatePolicyMapper.MapMany(source, projectId);
_logger.Information(Component, "Infisical certificate policy list retrieval was successful.");
return mapped;
}
catch (Exception)
{
_logger.Error(Component, "Infisical certificate policy list retrieval failed.");
throw;
}
}
public InfisicalCertificatePolicy GetCertificatePolicy(InfisicalConnection connection, string certificatePolicyId, string projectId)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
if (string.IsNullOrEmpty(certificatePolicyId)) { throw new InfisicalConfigurationException("CertificatePolicyId is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "certificatePolicyId", certificatePolicyId } };
List<KeyValuePair<string, string>> query = null;
if (!string.IsNullOrEmpty(projectId))
{
query = new List<KeyValuePair<string, string>> { new KeyValuePair<string, string>("projectId", projectId) };
}
try
{
_logger.Information(Component, string.Concat("Attempting to retrieve Infisical certificate policy '", certificatePolicyId, "'. Please Wait..."));
InfisicalHttpResponse response = _invoker.InvokeWithCandidateFallback(connection, InfisicalEndpointNames.GetCertificatePolicy, "GetCertificatePolicy", pathParameters, query, null);
string body = response.Body;
response.Clear();
InfisicalCertificatePolicyResponseDto inner = ParseCertificatePolicySingleBody(body);
InfisicalCertificatePolicy mapped = InfisicalCertificatePolicyMapper.Map(inner, projectId);
_logger.Information(Component, "Infisical certificate policy retrieval was successful.");
return mapped;
}
catch (Exception)
{
_logger.Error(Component, "Infisical certificate policy retrieval failed.");
throw;
}
}
private List<InfisicalCertificatePolicyResponseDto> ParseCertificatePolicyListBody(string body)
{
if (string.IsNullOrEmpty(body)) { return null; }
JToken token = JToken.Parse(body);
if (token.Type == JTokenType.Array)
{
return token.ToObject<List<InfisicalCertificatePolicyResponseDto>>();
}
InfisicalCertificatePolicyListResponseDto wrapper = token.ToObject<InfisicalCertificatePolicyListResponseDto>();
return wrapper != null ? wrapper.CertificatePolicies : null;
}
private InfisicalCertificatePolicyResponseDto ParseCertificatePolicySingleBody(string body)
{
if (string.IsNullOrEmpty(body)) { return null; }
JToken token = JToken.Parse(body);
if (token.Type != JTokenType.Object) { return null; }
JObject obj = (JObject)token;
if (obj["certificatePolicy"] is JObject inner) { return inner.ToObject<InfisicalCertificatePolicyResponseDto>(); }
return obj.ToObject<InfisicalCertificatePolicyResponseDto>();
}
public InfisicalCertificateBundle GetCertificateBundle(InfisicalConnection connection, string serialNumber)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
@@ -143,6 +629,228 @@ namespace PSInfisicalAPI.Pki
}
}
public InfisicalCertificateApplication[] ListCertificateApplications(InfisicalConnection connection, string projectId, int? limit, int? offset)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
if (string.IsNullOrEmpty(projectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
List<KeyValuePair<string, string>> query = new List<KeyValuePair<string, string>>();
if (limit.HasValue) { query.Add(new KeyValuePair<string, string>("limit", limit.Value.ToString(CultureInfo.InvariantCulture))); }
if (offset.HasValue) { query.Add(new KeyValuePair<string, string>("offset", offset.Value.ToString(CultureInfo.InvariantCulture))); }
Dictionary<string, string> headers = BuildProjectHeader(projectId);
try
{
_logger.Information(Component, "Attempting to list Infisical certificate applications. Please Wait...");
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.ListCertificateApplications, "ListCertificateApplications", null, query, null, headers);
string body = response.Body;
response.Clear();
List<InfisicalCertificateApplicationResponseDto> source = ParseApplicationListBody(body);
InfisicalCertificateApplication[] mapped = InfisicalCertificateApplicationMapper.MapMany(source, projectId);
_logger.Information(Component, "Infisical certificate application list retrieval was successful.");
return mapped;
}
catch (Exception)
{
_logger.Error(Component, "Infisical certificate application list retrieval failed.");
throw;
}
}
public InfisicalCertificateApplication GetCertificateApplication(InfisicalConnection connection, string applicationId, string projectId)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
if (string.IsNullOrEmpty(applicationId)) { throw new InfisicalConfigurationException("ApplicationId is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "applicationId", applicationId } };
Dictionary<string, string> headers = !string.IsNullOrEmpty(projectId) ? BuildProjectHeader(projectId) : null;
try
{
_logger.Information(Component, string.Concat("Attempting to retrieve Infisical certificate application '", applicationId, "'. Please Wait..."));
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.GetCertificateApplication, "GetCertificateApplication", pathParameters, null, null, headers);
string body = response.Body;
response.Clear();
InfisicalCertificateApplicationResponseDto inner = ParseApplicationSingleBody(body);
InfisicalCertificateApplication mapped = InfisicalCertificateApplicationMapper.Map(inner, projectId);
_logger.Information(Component, "Infisical certificate application retrieval was successful.");
return mapped;
}
catch (Exception)
{
_logger.Error(Component, "Infisical certificate application retrieval failed.");
throw;
}
}
public InfisicalCertificateApplication GetCertificateApplicationByName(InfisicalConnection connection, string name, string projectId)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
if (string.IsNullOrEmpty(name)) { throw new InfisicalConfigurationException("ApplicationName is required."); }
if (string.IsNullOrEmpty(projectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "name", name } };
Dictionary<string, string> headers = BuildProjectHeader(projectId);
try
{
_logger.Information(Component, string.Concat("Attempting to retrieve Infisical certificate application '", name, "' by name. Please Wait..."));
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.GetCertificateApplicationByName, "GetCertificateApplicationByName", pathParameters, null, null, headers);
string body = response.Body;
response.Clear();
InfisicalCertificateApplicationResponseDto inner = ParseApplicationSingleBody(body);
InfisicalCertificateApplication mapped = InfisicalCertificateApplicationMapper.Map(inner, projectId);
_logger.Information(Component, "Infisical certificate application (by name) retrieval was successful.");
return mapped;
}
catch (Exception)
{
_logger.Error(Component, "Infisical certificate application (by name) retrieval failed.");
throw;
}
}
public InfisicalCertificateApplicationProfileAttachment[] ListCertificateApplicationProfiles(InfisicalConnection connection, string applicationId, string projectId)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
if (string.IsNullOrEmpty(applicationId)) { throw new InfisicalConfigurationException("ApplicationId is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "applicationId", applicationId } };
Dictionary<string, string> headers = !string.IsNullOrEmpty(projectId) ? BuildProjectHeader(projectId) : null;
try
{
_logger.Information(Component, string.Concat("Attempting to list profile attachments for Infisical certificate application '", applicationId, "'. Please Wait..."));
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.ListCertificateApplicationProfiles, "ListCertificateApplicationProfiles", pathParameters, null, null, headers);
string body = response.Body;
response.Clear();
List<InfisicalCertificateApplicationProfileAttachmentDto> source = ParseApplicationProfilesBody(body);
InfisicalCertificateApplicationProfileAttachment[] mapped = InfisicalCertificateApplicationMapper.MapAttachments(source);
_logger.Information(Component, "Infisical certificate application profile attachment listing was successful.");
return mapped;
}
catch (Exception)
{
_logger.Error(Component, "Infisical certificate application profile attachment listing failed.");
throw;
}
}
public InfisicalCertificateApplicationEnrollment GetCertificateApplicationEnrollment(InfisicalConnection connection, string applicationId, string profileId, string projectId)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
if (string.IsNullOrEmpty(applicationId)) { throw new InfisicalConfigurationException("ApplicationId is required."); }
if (string.IsNullOrEmpty(profileId)) { throw new InfisicalConfigurationException("ProfileId is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string>
{
{ "applicationId", applicationId },
{ "profileId", profileId }
};
Dictionary<string, string> headers = !string.IsNullOrEmpty(projectId) ? BuildProjectHeader(projectId) : null;
try
{
_logger.Information(Component, string.Concat("Attempting to retrieve enrollment for application '", applicationId, "' / profile '", profileId, "'. Please Wait..."));
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.GetCertificateApplicationEnrollment, "GetCertificateApplicationEnrollment", pathParameters, null, null, headers);
InfisicalCertificateApplicationEnrollmentResponseDto dto = _serializer.Deserialize<InfisicalCertificateApplicationEnrollmentResponseDto>(response.Body);
response.Clear();
InfisicalCertificateApplicationEnrollment mapped = InfisicalCertificateApplicationMapper.MapEnrollment(dto);
_logger.Information(Component, "Infisical certificate application enrollment retrieval was successful.");
return mapped;
}
catch (Exception)
{
_logger.Error(Component, "Infisical certificate application enrollment retrieval failed.");
throw;
}
}
public string GenerateScepDynamicChallenge(InfisicalConnection connection, string applicationId, string profileId)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
if (string.IsNullOrEmpty(applicationId)) { throw new InfisicalConfigurationException("ApplicationId is required."); }
if (string.IsNullOrEmpty(profileId)) { throw new InfisicalConfigurationException("ProfileId is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string>
{
{ "applicationId", applicationId },
{ "profileId", profileId }
};
Dictionary<string, string> headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "Accept", "text/plain" }
};
try
{
_logger.Information(Component, string.Concat("Attempting to generate SCEP dynamic challenge for application '", applicationId, "' / profile '", profileId, "'. Please Wait..."));
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.GenerateScepDynamicChallenge, "GenerateScepDynamicChallenge", pathParameters, null, string.Empty, headers);
string body = response.Body != null ? response.Body.Trim() : null;
response.Clear();
if (string.IsNullOrEmpty(body)) { throw new InfisicalApiException("SCEP dynamic challenge response was empty."); }
_logger.Information(Component, "Infisical SCEP dynamic challenge generation was successful.");
return body;
}
catch (Exception)
{
_logger.Error(Component, "Infisical SCEP dynamic challenge generation failed.");
throw;
}
}
private static Dictionary<string, string> BuildProjectHeader(string projectId)
{
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "x-infisical-project-id", projectId }
};
}
private List<InfisicalCertificateApplicationResponseDto> ParseApplicationListBody(string body)
{
if (string.IsNullOrEmpty(body)) { return null; }
JToken token = JToken.Parse(body);
if (token.Type == JTokenType.Array)
{
return token.ToObject<List<InfisicalCertificateApplicationResponseDto>>();
}
InfisicalCertificateApplicationListResponseDto wrapper = token.ToObject<InfisicalCertificateApplicationListResponseDto>();
return wrapper != null ? wrapper.Applications : null;
}
private InfisicalCertificateApplicationResponseDto ParseApplicationSingleBody(string body)
{
if (string.IsNullOrEmpty(body)) { return null; }
JToken token = JToken.Parse(body);
if (token.Type != JTokenType.Object) { return null; }
JObject obj = (JObject)token;
if (obj["application"] is JObject inner) { return inner.ToObject<InfisicalCertificateApplicationResponseDto>(); }
return obj.ToObject<InfisicalCertificateApplicationResponseDto>();
}
private List<InfisicalCertificateApplicationProfileAttachmentDto> ParseApplicationProfilesBody(string body)
{
if (string.IsNullOrEmpty(body)) { return null; }
JToken token = JToken.Parse(body);
if (token.Type == JTokenType.Array)
{
return token.ToObject<List<InfisicalCertificateApplicationProfileAttachmentDto>>();
}
InfisicalCertificateApplicationProfilesResponseDto wrapper = token.ToObject<InfisicalCertificateApplicationProfilesResponseDto>();
return wrapper != null ? wrapper.Profiles : null;
}
internal static InfisicalCertificateSearchRequestDto BuildSearchRequest(InfisicalCertificateSearchQuery query)
{
return new InfisicalCertificateSearchRequestDto
@@ -0,0 +1,43 @@
using System.Collections.Generic;
using Newtonsoft.Json;
namespace PSInfisicalAPI.Pki
{
internal sealed class InfisicalPkiSubscriberResponseDto
{
[JsonProperty("id")] public string Id { get; set; }
[JsonProperty("projectId")] public string ProjectId { get; set; }
[JsonProperty("caId")] public string CaId { get; set; }
[JsonProperty("name")] public string Name { get; set; }
[JsonProperty("commonName")] public string CommonName { get; set; }
[JsonProperty("status")] public string Status { get; set; }
[JsonProperty("ttl")] public string Ttl { get; set; }
[JsonProperty("subjectAlternativeNames")] public List<string> SubjectAlternativeNames { get; set; }
[JsonProperty("keyUsages")] public List<string> KeyUsages { get; set; }
[JsonProperty("extendedKeyUsages")] public List<string> ExtendedKeyUsages { get; set; }
[JsonProperty("enableAutoRenewal")] public bool? EnableAutoRenewal { get; set; }
[JsonProperty("autoRenewalPeriodInDays")] public int? AutoRenewalPeriodInDays { get; set; }
[JsonProperty("lastOperationStatus")] public string LastOperationStatus { get; set; }
[JsonProperty("lastOperationMessage")] public string LastOperationMessage { get; set; }
[JsonProperty("lastOperationAt")] public string LastOperationAt { get; set; }
[JsonProperty("createdAt")] public string CreatedAt { get; set; }
[JsonProperty("updatedAt")] public string UpdatedAt { get; set; }
[JsonProperty("properties")] public InfisicalPkiSubscriberPropertiesDto Properties { get; set; }
}
internal sealed class InfisicalPkiSubscriberPropertiesDto
{
[JsonProperty("azureTemplateType")] public string AzureTemplateType { get; set; }
[JsonProperty("organization")] public string Organization { get; set; }
[JsonProperty("organizationalUnit")] public string OrganizationalUnit { get; set; }
[JsonProperty("country")] public string Country { get; set; }
[JsonProperty("state")] public string State { get; set; }
[JsonProperty("locality")] public string Locality { get; set; }
[JsonProperty("emailAddress")] public string EmailAddress { get; set; }
}
internal sealed class InfisicalPkiSubscriberListResponseDto
{
[JsonProperty("subscribers")] public List<InfisicalPkiSubscriberResponseDto> Subscribers { get; set; }
}
}
@@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using PSInfisicalAPI.Models;
namespace PSInfisicalAPI.Pki
{
internal static class InfisicalPkiSubscriberMapper
{
public static InfisicalPkiSubscriber Map(InfisicalPkiSubscriberResponseDto dto, string fallbackProjectId)
{
if (dto == null)
{
return null;
}
return new InfisicalPkiSubscriber
{
Id = dto.Id,
ProjectId = !string.IsNullOrEmpty(dto.ProjectId) ? dto.ProjectId : fallbackProjectId,
CaId = dto.CaId,
Name = dto.Name,
CommonName = dto.CommonName,
Status = dto.Status,
Ttl = dto.Ttl,
SubjectAlternativeNames = dto.SubjectAlternativeNames != null ? dto.SubjectAlternativeNames.ToArray() : null,
KeyUsages = dto.KeyUsages != null ? dto.KeyUsages.ToArray() : null,
ExtendedKeyUsages = dto.ExtendedKeyUsages != null ? dto.ExtendedKeyUsages.ToArray() : null,
EnableAutoRenewal = dto.EnableAutoRenewal,
AutoRenewalPeriodInDays = dto.AutoRenewalPeriodInDays,
LastOperationStatus = dto.LastOperationStatus,
LastOperationMessage = dto.LastOperationMessage,
LastOperationAtUtc = ParseTimestamp(dto.LastOperationAt),
CreatedAtUtc = ParseTimestamp(dto.CreatedAt),
UpdatedAtUtc = ParseTimestamp(dto.UpdatedAt),
Properties = MapProperties(dto.Properties)
};
}
public static InfisicalPkiSubscriber[] MapMany(IEnumerable<InfisicalPkiSubscriberResponseDto> items, string fallbackProjectId)
{
if (items == null)
{
return Array.Empty<InfisicalPkiSubscriber>();
}
List<InfisicalPkiSubscriber> results = new List<InfisicalPkiSubscriber>();
foreach (InfisicalPkiSubscriberResponseDto dto in items)
{
InfisicalPkiSubscriber mapped = Map(dto, fallbackProjectId);
if (mapped != null)
{
results.Add(mapped);
}
}
return results.ToArray();
}
private static InfisicalPkiSubscriberProperties MapProperties(InfisicalPkiSubscriberPropertiesDto dto)
{
if (dto == null)
{
return null;
}
return new InfisicalPkiSubscriberProperties
{
AzureTemplateType = dto.AzureTemplateType,
Organization = dto.Organization,
OrganizationalUnit = dto.OrganizationalUnit,
Country = dto.Country,
State = dto.State,
Locality = dto.Locality,
EmailAddress = dto.EmailAddress
};
}
private static DateTimeOffset? ParseTimestamp(string value)
{
if (string.IsNullOrEmpty(value))
{
return null;
}
DateTimeOffset parsed;
if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out parsed))
{
return parsed;
}
return null;
}
}
}
@@ -0,0 +1,10 @@
namespace PSInfisicalAPI.Pki
{
public enum InfisicalPrivateKeyProtection
{
Exportable = 0,
LocalOnly = 1,
NonExportable = 2,
Ephemeral = 3
}
}
@@ -0,0 +1,73 @@
using System.Collections.Generic;
using Newtonsoft.Json;
namespace PSInfisicalAPI.Pki
{
internal sealed class InfisicalSignCertificateBySubscriberRequestDto
{
[JsonProperty("projectId")] public string ProjectId { get; set; }
[JsonProperty("csr")] public string Csr { get; set; }
}
internal sealed class InfisicalSignCertificateByCaRequestDto
{
[JsonProperty("csr")] public string Csr { get; set; }
[JsonProperty("commonName", NullValueHandling = NullValueHandling.Ignore)] public string CommonName { get; set; }
[JsonProperty("altNames", NullValueHandling = NullValueHandling.Ignore)] public string AltNames { get; set; }
[JsonProperty("ttl", NullValueHandling = NullValueHandling.Ignore)] public string Ttl { get; set; }
[JsonProperty("notBefore", NullValueHandling = NullValueHandling.Ignore)] public string NotBefore { get; set; }
[JsonProperty("notAfter", NullValueHandling = NullValueHandling.Ignore)] public string NotAfter { get; set; }
[JsonProperty("friendlyName", NullValueHandling = NullValueHandling.Ignore)] public string FriendlyName { get; set; }
[JsonProperty("pkiCollectionId", NullValueHandling = NullValueHandling.Ignore)] public string PkiCollectionId { get; set; }
[JsonProperty("keyUsages", NullValueHandling = NullValueHandling.Ignore)] public List<string> KeyUsages { get; set; }
[JsonProperty("extendedKeyUsages", NullValueHandling = NullValueHandling.Ignore)] public List<string> ExtendedKeyUsages { get; set; }
}
internal sealed class InfisicalSignCertificateResponseDto
{
[JsonProperty("certificate")] public string Certificate { get; set; }
[JsonProperty("certificateChain")] public string CertificateChain { get; set; }
[JsonProperty("issuingCaCertificate")] public string IssuingCaCertificate { get; set; }
[JsonProperty("serialNumber")] public string SerialNumber { get; set; }
}
internal sealed class InfisicalIssueCertificateByProfileRequestDto
{
[JsonProperty("profileId")] public string ProfileId { get; set; }
[JsonProperty("csr", NullValueHandling = NullValueHandling.Ignore)] public string Csr { get; set; }
[JsonProperty("attributes", NullValueHandling = NullValueHandling.Ignore)] public InfisicalIssueCertificateAttributesDto Attributes { get; set; }
}
internal sealed class InfisicalIssueCertificateAttributesDto
{
[JsonProperty("commonName", NullValueHandling = NullValueHandling.Ignore)] public string CommonName { get; set; }
[JsonProperty("organization", NullValueHandling = NullValueHandling.Ignore)] public string Organization { get; set; }
[JsonProperty("organizationalUnit", NullValueHandling = NullValueHandling.Ignore)] public string OrganizationalUnit { get; set; }
[JsonProperty("country", NullValueHandling = NullValueHandling.Ignore)] public string Country { get; set; }
[JsonProperty("state", NullValueHandling = NullValueHandling.Ignore)] public string State { get; set; }
[JsonProperty("locality", NullValueHandling = NullValueHandling.Ignore)] public string Locality { get; set; }
[JsonProperty("ttl", NullValueHandling = NullValueHandling.Ignore)] public string Ttl { get; set; }
[JsonProperty("notBefore", NullValueHandling = NullValueHandling.Ignore)] public string NotBefore { get; set; }
[JsonProperty("notAfter", NullValueHandling = NullValueHandling.Ignore)] public string NotAfter { get; set; }
[JsonProperty("keyUsages", NullValueHandling = NullValueHandling.Ignore)] public List<string> KeyUsages { get; set; }
[JsonProperty("extendedKeyUsages", NullValueHandling = NullValueHandling.Ignore)] public List<string> ExtendedKeyUsages { get; set; }
}
internal sealed class InfisicalIssueCertificateResponseDto
{
[JsonProperty("certificate")] public InfisicalIssueCertificateInnerDto Certificate { get; set; }
[JsonProperty("certificateRequestId")] public string CertificateRequestId { get; set; }
[JsonProperty("status")] public string Status { get; set; }
[JsonProperty("message")] public string Message { get; set; }
}
internal sealed class InfisicalIssueCertificateInnerDto
{
[JsonProperty("certificate")] public string Certificate { get; set; }
[JsonProperty("certificateChain")] public string CertificateChain { get; set; }
[JsonProperty("issuingCaCertificate")] public string IssuingCaCertificate { get; set; }
[JsonProperty("serialNumber")] public string SerialNumber { get; set; }
[JsonProperty("certificateId")] public string CertificateId { get; set; }
[JsonProperty("privateKey")] public string PrivateKey { get; set; }
}
}
@@ -27,13 +27,25 @@ namespace PSInfisicalAPI.Projects
}
public InfisicalProject[] List(InfisicalConnection connection)
{
return List(connection, null, false);
}
public InfisicalProject[] List(InfisicalConnection connection, string type, bool includeRoles)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
List<KeyValuePair<string, string>> queryParameters = new List<KeyValuePair<string, string>>();
queryParameters.Add(new KeyValuePair<string, string>("includeRoles", includeRoles ? "true" : "false"));
if (!string.IsNullOrEmpty(type))
{
queryParameters.Add(new KeyValuePair<string, string>("type", type));
}
try
{
_logger.Information(Component, "Attempting to list Infisical projects. Please Wait...");
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.ListProjects, "ListProjects", null, null, null);
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.ListProjects, "ListProjects", null, queryParameters, null);
InfisicalProjectListResponseDto dto = _serializer.Deserialize<InfisicalProjectListResponseDto>(response.Body);
response.Clear();
@@ -32,13 +32,11 @@ namespace PSInfisicalAPI.Secrets
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
if (query == null) { throw new ArgumentNullException(nameof(query)); }
string resolvedProjectId = FirstNonEmpty(query.ProjectId, connection.ProjectId);
List<KeyValuePair<string, string>> queryParameters = new List<KeyValuePair<string, string>>();
AddIfNotNull(queryParameters, "workspaceId", resolvedProjectId);
AddIfNotNull(queryParameters, "projectId", resolvedProjectId);
AddIfNotNull(queryParameters, "environment", FirstNonEmpty(query.Environment, connection.Environment));
AddIfNotNull(queryParameters, "secretPath", FirstNonEmpty(query.SecretPath, connection.DefaultSecretPath, "/"));
AddIfNotNull(queryParameters, "workspaceId", query.ProjectId);
AddIfNotNull(queryParameters, "projectId", query.ProjectId);
AddIfNotNull(queryParameters, "environment", query.Environment);
AddIfNotNull(queryParameters, "secretPath", FirstNonEmpty(query.SecretPath, "/"));
queryParameters.Add(new KeyValuePair<string, string>("recursive", query.Recursive ? "true" : "false"));
if (query.IncludeImports.HasValue) { queryParameters.Add(new KeyValuePair<string, string>("includeImports", query.IncludeImports.Value ? "true" : "false")); }
if (query.IncludePersonalOverrides) { queryParameters.Add(new KeyValuePair<string, string>("includePersonalOverrides", "true")); }
@@ -96,13 +94,11 @@ namespace PSInfisicalAPI.Secrets
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "secretName", query.SecretName } };
string resolvedProjectId = FirstNonEmpty(query.ProjectId, connection.ProjectId);
List<KeyValuePair<string, string>> queryParameters = new List<KeyValuePair<string, string>>();
AddIfNotNull(queryParameters, "workspaceId", resolvedProjectId);
AddIfNotNull(queryParameters, "projectId", resolvedProjectId);
AddIfNotNull(queryParameters, "environment", FirstNonEmpty(query.Environment, connection.Environment));
AddIfNotNull(queryParameters, "secretPath", FirstNonEmpty(query.SecretPath, connection.DefaultSecretPath, "/"));
AddIfNotNull(queryParameters, "workspaceId", query.ProjectId);
AddIfNotNull(queryParameters, "projectId", query.ProjectId);
AddIfNotNull(queryParameters, "environment", query.Environment);
AddIfNotNull(queryParameters, "secretPath", FirstNonEmpty(query.SecretPath, "/"));
AddIfNotNull(queryParameters, "type", string.IsNullOrEmpty(query.Type) ? "shared" : query.Type.ToLowerInvariant());
if (query.Version.HasValue) { queryParameters.Add(new KeyValuePair<string, string>("version", query.Version.Value.ToString(CultureInfo.InvariantCulture))); }
if (query.ViewSecretValue.HasValue) { queryParameters.Add(new KeyValuePair<string, string>("viewSecretValue", query.ViewSecretValue.Value ? "true" : "false")); }
@@ -143,17 +139,15 @@ namespace PSInfisicalAPI.Secrets
if (string.IsNullOrEmpty(request.SecretName)) { throw new InfisicalConfigurationException("SecretName is required."); }
if (request.SecretValue == null) { throw new InfisicalConfigurationException("SecretValue is required."); }
string resolvedProjectId = FirstNonEmpty(request.ProjectId, connection.ProjectId);
string resolvedEnvironment = FirstNonEmpty(request.Environment, connection.Environment);
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(resolvedEnvironment)) { throw new InfisicalConfigurationException("Environment is required."); }
if (string.IsNullOrEmpty(request.ProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(request.Environment)) { throw new InfisicalConfigurationException("Environment is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "secretName", request.SecretName } };
InfisicalSecretCreateRequestDto dtoRequest = new InfisicalSecretCreateRequestDto
{
WorkspaceId = resolvedProjectId,
Environment = resolvedEnvironment,
SecretPath = FirstNonEmpty(request.SecretPath, connection.DefaultSecretPath, "/"),
WorkspaceId = request.ProjectId,
Environment = request.Environment,
SecretPath = FirstNonEmpty(request.SecretPath, "/"),
Type = string.IsNullOrEmpty(request.Type) ? "shared" : request.Type.ToLowerInvariant(),
SecretValue = request.SecretValue,
SecretComment = request.SecretComment,
@@ -186,17 +180,15 @@ namespace PSInfisicalAPI.Secrets
if (request == null) { throw new ArgumentNullException(nameof(request)); }
if (string.IsNullOrEmpty(request.SecretName)) { throw new InfisicalConfigurationException("SecretName is required."); }
string resolvedProjectId = FirstNonEmpty(request.ProjectId, connection.ProjectId);
string resolvedEnvironment = FirstNonEmpty(request.Environment, connection.Environment);
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(resolvedEnvironment)) { throw new InfisicalConfigurationException("Environment is required."); }
if (string.IsNullOrEmpty(request.ProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(request.Environment)) { throw new InfisicalConfigurationException("Environment is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "secretName", request.SecretName } };
InfisicalSecretUpdateRequestDto dtoRequest = new InfisicalSecretUpdateRequestDto
{
WorkspaceId = resolvedProjectId,
Environment = resolvedEnvironment,
SecretPath = FirstNonEmpty(request.SecretPath, connection.DefaultSecretPath, "/"),
WorkspaceId = request.ProjectId,
Environment = request.Environment,
SecretPath = FirstNonEmpty(request.SecretPath, "/"),
Type = string.IsNullOrEmpty(request.Type) ? "shared" : request.Type.ToLowerInvariant(),
SecretValue = request.SecretValue,
SecretComment = request.SecretComment,
@@ -230,10 +222,8 @@ namespace PSInfisicalAPI.Secrets
if (request == null) { throw new ArgumentNullException(nameof(request)); }
if (request.Secrets == null || request.Secrets.Length == 0) { throw new InfisicalConfigurationException("At least one secret is required."); }
string resolvedProjectId = FirstNonEmpty(request.ProjectId, connection.ProjectId);
string resolvedEnvironment = FirstNonEmpty(request.Environment, connection.Environment);
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(resolvedEnvironment)) { throw new InfisicalConfigurationException("Environment is required."); }
if (string.IsNullOrEmpty(request.ProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(request.Environment)) { throw new InfisicalConfigurationException("Environment is required."); }
List<InfisicalSecretBatchCreateItemDto> items = new List<InfisicalSecretBatchCreateItemDto>(request.Secrets.Length);
foreach (InfisicalBulkCreateSecretItem item in request.Secrets)
@@ -253,10 +243,10 @@ namespace PSInfisicalAPI.Secrets
InfisicalSecretBatchCreateRequestDto dtoRequest = new InfisicalSecretBatchCreateRequestDto
{
WorkspaceId = resolvedProjectId,
ProjectId = resolvedProjectId,
Environment = resolvedEnvironment,
SecretPath = FirstNonEmpty(request.SecretPath, connection.DefaultSecretPath, "/"),
WorkspaceId = request.ProjectId,
ProjectId = request.ProjectId,
Environment = request.Environment,
SecretPath = FirstNonEmpty(request.SecretPath, "/"),
Secrets = items
};
string body = _serializer.Serialize(dtoRequest);
@@ -285,10 +275,8 @@ namespace PSInfisicalAPI.Secrets
if (request == null) { throw new ArgumentNullException(nameof(request)); }
if (request.Secrets == null || request.Secrets.Length == 0) { throw new InfisicalConfigurationException("At least one secret is required."); }
string resolvedProjectId = FirstNonEmpty(request.ProjectId, connection.ProjectId);
string resolvedEnvironment = FirstNonEmpty(request.Environment, connection.Environment);
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(resolvedEnvironment)) { throw new InfisicalConfigurationException("Environment is required."); }
if (string.IsNullOrEmpty(request.ProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(request.Environment)) { throw new InfisicalConfigurationException("Environment is required."); }
List<InfisicalSecretBatchUpdateItemDto> items = new List<InfisicalSecretBatchUpdateItemDto>(request.Secrets.Length);
foreach (InfisicalBulkUpdateSecretItem item in request.Secrets)
@@ -309,10 +297,10 @@ namespace PSInfisicalAPI.Secrets
InfisicalSecretBatchUpdateRequestDto dtoRequest = new InfisicalSecretBatchUpdateRequestDto
{
WorkspaceId = resolvedProjectId,
ProjectId = resolvedProjectId,
Environment = resolvedEnvironment,
SecretPath = FirstNonEmpty(request.SecretPath, connection.DefaultSecretPath, "/"),
WorkspaceId = request.ProjectId,
ProjectId = request.ProjectId,
Environment = request.Environment,
SecretPath = FirstNonEmpty(request.SecretPath, "/"),
Mode = request.Mode,
Secrets = items
};
@@ -342,10 +330,8 @@ namespace PSInfisicalAPI.Secrets
if (request == null) { throw new ArgumentNullException(nameof(request)); }
if (request.SecretNames == null || request.SecretNames.Length == 0) { throw new InfisicalConfigurationException("At least one secret name is required."); }
string resolvedProjectId = FirstNonEmpty(request.ProjectId, connection.ProjectId);
string resolvedEnvironment = FirstNonEmpty(request.Environment, connection.Environment);
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(resolvedEnvironment)) { throw new InfisicalConfigurationException("Environment is required."); }
if (string.IsNullOrEmpty(request.ProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(request.Environment)) { throw new InfisicalConfigurationException("Environment is required."); }
List<InfisicalSecretBatchDeleteItemDto> items = new List<InfisicalSecretBatchDeleteItemDto>(request.SecretNames.Length);
foreach (string name in request.SecretNames)
@@ -356,10 +342,10 @@ namespace PSInfisicalAPI.Secrets
InfisicalSecretBatchDeleteRequestDto dtoRequest = new InfisicalSecretBatchDeleteRequestDto
{
WorkspaceId = resolvedProjectId,
ProjectId = resolvedProjectId,
Environment = resolvedEnvironment,
SecretPath = FirstNonEmpty(request.SecretPath, connection.DefaultSecretPath, "/"),
WorkspaceId = request.ProjectId,
ProjectId = request.ProjectId,
Environment = request.Environment,
SecretPath = FirstNonEmpty(request.SecretPath, "/"),
Secrets = items
};
string body = _serializer.Serialize(dtoRequest);
@@ -384,13 +370,11 @@ namespace PSInfisicalAPI.Secrets
if (request == null) { throw new ArgumentNullException(nameof(request)); }
if (request.SecretIds == null || request.SecretIds.Length == 0) { throw new InfisicalConfigurationException("At least one SecretId is required."); }
string resolvedProjectId = FirstNonEmpty(request.ProjectId, connection.ProjectId);
string resolvedSourceEnv = FirstNonEmpty(request.SourceEnvironment, connection.Environment);
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(resolvedSourceEnv)) { throw new InfisicalConfigurationException("SourceEnvironment is required."); }
if (string.IsNullOrEmpty(request.ProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(request.SourceEnvironment)) { throw new InfisicalConfigurationException("SourceEnvironment is required."); }
if (string.IsNullOrEmpty(request.DestinationEnvironment)) { throw new InfisicalConfigurationException("DestinationEnvironment is required."); }
string resolvedSourcePath = FirstNonEmpty(request.SourceSecretPath, connection.DefaultSecretPath, "/");
string resolvedSourcePath = FirstNonEmpty(request.SourceSecretPath, "/");
string resolvedDestPath = FirstNonEmpty(request.DestinationSecretPath, resolvedSourcePath);
InfisicalSecretDuplicateAttributesDto attributes = null;
@@ -407,8 +391,8 @@ namespace PSInfisicalAPI.Secrets
InfisicalSecretDuplicateRequestDto dtoRequest = new InfisicalSecretDuplicateRequestDto
{
ProjectId = resolvedProjectId,
SourceEnvironment = resolvedSourceEnv,
ProjectId = request.ProjectId,
SourceEnvironment = request.SourceEnvironment,
DestinationEnvironment = request.DestinationEnvironment,
SourceSecretPath = resolvedSourcePath,
DestinationSecretPath = resolvedDestPath,
@@ -454,17 +438,15 @@ namespace PSInfisicalAPI.Secrets
if (request == null) { throw new ArgumentNullException(nameof(request)); }
if (string.IsNullOrEmpty(request.SecretName)) { throw new InfisicalConfigurationException("SecretName is required."); }
string resolvedProjectId = FirstNonEmpty(request.ProjectId, connection.ProjectId);
string resolvedEnvironment = FirstNonEmpty(request.Environment, connection.Environment);
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(resolvedEnvironment)) { throw new InfisicalConfigurationException("Environment is required."); }
if (string.IsNullOrEmpty(request.ProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(request.Environment)) { throw new InfisicalConfigurationException("Environment is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "secretName", request.SecretName } };
InfisicalSecretDeleteRequestDto dtoRequest = new InfisicalSecretDeleteRequestDto
{
WorkspaceId = resolvedProjectId,
Environment = resolvedEnvironment,
SecretPath = FirstNonEmpty(request.SecretPath, connection.DefaultSecretPath, "/"),
WorkspaceId = request.ProjectId,
Environment = request.Environment,
SecretPath = FirstNonEmpty(request.SecretPath, "/"),
Type = string.IsNullOrEmpty(request.Type) ? "shared" : request.Type.ToLowerInvariant()
};
string body = _serializer.Serialize(dtoRequest);
@@ -625,15 +607,14 @@ namespace PSInfisicalAPI.Secrets
private static InfisicalApiException BuildApiException(InfisicalHttpResponse response, InfisicalEndpointDefinition definition)
{
InfisicalApiException exception = new InfisicalApiException(string.Concat(
"Infisical API returned ",
response.StatusCode.ToString(CultureInfo.InvariantCulture),
" (", response.ReasonPhrase ?? string.Empty, ")."));
string message = InfisicalApiErrorEnvelope.BuildExceptionMessage(response.StatusCode, response.ReasonPhrase, response.Body);
InfisicalApiException exception = new InfisicalApiException(message);
exception.StatusCode = response.StatusCode;
exception.ReasonPhrase = response.ReasonPhrase;
exception.EndpointName = definition.Name;
exception.RequestMethod = definition.Method;
exception.SanitizedBody = response.Body;
InfisicalApiErrorEnvelope.Enrich(exception, response.Body);
return exception;
}
+13 -24
View File
@@ -29,10 +29,9 @@ namespace PSInfisicalAPI.Tags
public InfisicalTag[] List(InfisicalConnection connection, string projectId)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(projectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", resolvedProjectId } };
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", projectId } };
try
{
@@ -42,7 +41,7 @@ namespace PSInfisicalAPI.Tags
response.Clear();
List<InfisicalTagResponseDto> source = dto != null ? (dto.WorkspaceTags ?? dto.Tags) : null;
InfisicalTag[] mapped = InfisicalTagMapper.MapMany(source, resolvedProjectId);
InfisicalTag[] mapped = InfisicalTagMapper.MapMany(source, projectId);
_logger.Information(Component, "Infisical tag list retrieval was successful.");
return mapped;
}
@@ -56,11 +55,10 @@ namespace PSInfisicalAPI.Tags
public InfisicalTag Retrieve(InfisicalConnection connection, string projectId, string tagSlugOrId)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(projectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(tagSlugOrId)) { throw new InfisicalConfigurationException("Tag slug or id is required."); }
InfisicalTag[] all = List(connection, resolvedProjectId);
InfisicalTag[] all = List(connection, projectId);
foreach (InfisicalTag tag in all)
{
if (string.Equals(tag.Id, tagSlugOrId, StringComparison.OrdinalIgnoreCase) ||
@@ -76,11 +74,10 @@ namespace PSInfisicalAPI.Tags
public InfisicalTag Create(InfisicalConnection connection, string projectId, string slug, string name, string color)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(projectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(slug)) { throw new InfisicalConfigurationException("Slug is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", resolvedProjectId } };
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", projectId } };
InfisicalTagCreateRequestDto request = new InfisicalTagCreateRequestDto { Slug = slug, Name = name, Color = color };
string body = _serializer.Serialize(request);
@@ -92,7 +89,7 @@ namespace PSInfisicalAPI.Tags
response.Clear();
InfisicalTagResponseDto inner = dto != null ? (dto.WorkspaceTag ?? dto.Tag) : null;
InfisicalTag mapped = InfisicalTagMapper.Map(inner, resolvedProjectId);
InfisicalTag mapped = InfisicalTagMapper.Map(inner, projectId);
_logger.Information(Component, "Infisical tag creation was successful.");
return mapped;
}
@@ -106,11 +103,10 @@ namespace PSInfisicalAPI.Tags
public InfisicalTag Update(InfisicalConnection connection, string projectId, string tagId, string slug, string name, string color)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(projectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(tagId)) { throw new InfisicalConfigurationException("TagId is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", resolvedProjectId }, { "tagId", tagId } };
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", projectId }, { "tagId", tagId } };
InfisicalTagUpdateRequestDto request = new InfisicalTagUpdateRequestDto { Slug = slug, Name = name, Color = color };
string body = _serializer.Serialize(request);
@@ -122,7 +118,7 @@ namespace PSInfisicalAPI.Tags
response.Clear();
InfisicalTagResponseDto inner = dto != null ? (dto.WorkspaceTag ?? dto.Tag) : null;
InfisicalTag mapped = InfisicalTagMapper.Map(inner, resolvedProjectId);
InfisicalTag mapped = InfisicalTagMapper.Map(inner, projectId);
_logger.Information(Component, "Infisical tag update was successful.");
return mapped;
}
@@ -136,11 +132,10 @@ namespace PSInfisicalAPI.Tags
public void Delete(InfisicalConnection connection, string projectId, string tagId)
{
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(projectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
if (string.IsNullOrEmpty(tagId)) { throw new InfisicalConfigurationException("TagId is required."); }
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", resolvedProjectId }, { "tagId", tagId } };
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", projectId }, { "tagId", tagId } };
try
{
@@ -156,11 +151,5 @@ namespace PSInfisicalAPI.Tags
}
}
private static string FirstNonEmpty(params string[] values)
{
if (values == null) { return null; }
foreach (string value in values) { if (!string.IsNullOrEmpty(value)) { return value; } }
return null;
}
}
}