Merge pull request '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) from dev into main
Reviewed-on: #5
This commit was merged in pull request #5.
This commit is contained in:
+195
-4
@@ -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.
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user