├── images ├── aci.jpg ├── obo.png ├── obo01.jpg ├── wam01.jpg ├── wam02.jpg ├── wam03.jpg ├── wam04.jpg ├── federatedcreds.jpg └── linuxarcerror.jpg ├── source ├── lib │ ├── WAMHelper.dll │ ├── DeviceCodeHelper.dll │ ├── PSMSALNetHelper.dll │ ├── Microsoft.Identity.Client.dll │ ├── Microsoft.Identity.Client.Broker.dll │ ├── Microsoft.IdentityModel.Abstractions.dll │ ├── runtimes │ │ ├── win-x64 │ │ │ └── native │ │ │ │ └── msalruntime.dll │ │ └── win-arm64 │ │ │ └── native │ │ │ └── msalruntime_arm64.dll │ ├── Microsoft.Identity.Client.Extensions.Msal.dll │ └── Microsoft.Identity.Client.NativeInterop.dll ├── Examples │ ├── managed-identity │ │ ├── script.ps1 │ │ ├── deploy.ps1 │ │ └── Dockerfile │ └── aks-workloadidentity │ │ ├── sa-aad.yaml │ │ ├── pod-deploy-pwsh.yaml │ │ ├── Dockerfile │ │ ├── script.ps1 │ │ └── demo.ps1 ├── PSMSALNet.psm1 ├── prefix.ps1 ├── en-US │ └── about_PSMSALNet.help.txt ├── Public │ ├── Get-KVCertificateWithPublicKey.ps1 │ ├── Get-KVCertificateWithPrivateKey.ps1 │ ├── ConvertFrom-Jwt.ps1 │ └── ConvertTo-X509Certificate2.ps1 └── PSMSALNet.psd1 ├── tests ├── helpers │ ├── scomnewbie.cer │ ├── scomnewbie.pfx │ ├── testentracert.crt │ ├── testentracert.pfx │ ├── secret.enc │ ├── scomnewbie.crt │ ├── readme.txt │ ├── privatekey_rsa.key │ ├── wrongprivatekey_rsa.key │ ├── scomnewbie.pem │ └── scomnewbie2.pem ├── Unit │ └── Public │ │ ├── Get-EntraToken.Tests.ps1 │ │ ├── Get-KVCertificateWithPublicKey.Tests.ps1 │ │ ├── Get-KVCertificateWithPrivateKey.Tests.ps1 │ │ ├── ConvertFrom-Jwt.Tests.ps1 │ │ └── ConvertTo-X509Certificate2.Tests.ps1 └── QA │ └── module.tests.ps1 ├── CODE_OF_CONDUCT.md ├── .markdownlint.json ├── .gitignore ├── .build └── custom.build.ps1 ├── .github ├── ISSUE_TEMPLATE │ ├── General.md │ ├── config.yml │ ├── Resource_proposal.yml │ ├── Problem_with_resource.yml │ └── Problem_with_module.yml └── PULL_REQUEST_TEMPLATE.md ├── CONTRIBUTING.md ├── .gitattributes ├── RequiredModules.psd1 ├── codecov.yml ├── GitVersion.yml ├── LICENSE ├── SECURITY.md ├── .vscode ├── settings.json ├── analyzersettings.psd1 └── tasks.json ├── Resolve-Dependency.psd1 ├── CHANGELOG.md ├── azure-pipelines.yml ├── README.md └── Resolve-Dependency.ps1 /images/aci.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SCOMnewbie/PSMSALNet/HEAD/images/aci.jpg -------------------------------------------------------------------------------- /images/obo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SCOMnewbie/PSMSALNet/HEAD/images/obo.png -------------------------------------------------------------------------------- /images/obo01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SCOMnewbie/PSMSALNet/HEAD/images/obo01.jpg -------------------------------------------------------------------------------- /images/wam01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SCOMnewbie/PSMSALNet/HEAD/images/wam01.jpg -------------------------------------------------------------------------------- /images/wam02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SCOMnewbie/PSMSALNet/HEAD/images/wam02.jpg -------------------------------------------------------------------------------- /images/wam03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SCOMnewbie/PSMSALNet/HEAD/images/wam03.jpg -------------------------------------------------------------------------------- /images/wam04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SCOMnewbie/PSMSALNet/HEAD/images/wam04.jpg -------------------------------------------------------------------------------- /images/federatedcreds.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SCOMnewbie/PSMSALNet/HEAD/images/federatedcreds.jpg -------------------------------------------------------------------------------- /images/linuxarcerror.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SCOMnewbie/PSMSALNet/HEAD/images/linuxarcerror.jpg -------------------------------------------------------------------------------- /source/lib/WAMHelper.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SCOMnewbie/PSMSALNet/HEAD/source/lib/WAMHelper.dll -------------------------------------------------------------------------------- /tests/helpers/scomnewbie.cer: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SCOMnewbie/PSMSALNet/HEAD/tests/helpers/scomnewbie.cer -------------------------------------------------------------------------------- /tests/helpers/scomnewbie.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SCOMnewbie/PSMSALNet/HEAD/tests/helpers/scomnewbie.pfx -------------------------------------------------------------------------------- /source/lib/DeviceCodeHelper.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SCOMnewbie/PSMSALNet/HEAD/source/lib/DeviceCodeHelper.dll -------------------------------------------------------------------------------- /source/lib/PSMSALNetHelper.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SCOMnewbie/PSMSALNet/HEAD/source/lib/PSMSALNetHelper.dll -------------------------------------------------------------------------------- /tests/helpers/testentracert.crt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SCOMnewbie/PSMSALNet/HEAD/tests/helpers/testentracert.crt -------------------------------------------------------------------------------- /tests/helpers/testentracert.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SCOMnewbie/PSMSALNet/HEAD/tests/helpers/testentracert.pfx -------------------------------------------------------------------------------- /source/lib/Microsoft.Identity.Client.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SCOMnewbie/PSMSALNet/HEAD/source/lib/Microsoft.Identity.Client.dll -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | This project has adopted the [DSC Community Code of Conduct](https://dsccommunity.org/code_of_conduct). 4 | -------------------------------------------------------------------------------- /source/lib/Microsoft.Identity.Client.Broker.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SCOMnewbie/PSMSALNet/HEAD/source/lib/Microsoft.Identity.Client.Broker.dll -------------------------------------------------------------------------------- /source/lib/Microsoft.IdentityModel.Abstractions.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SCOMnewbie/PSMSALNet/HEAD/source/lib/Microsoft.IdentityModel.Abstractions.dll -------------------------------------------------------------------------------- /source/lib/runtimes/win-x64/native/msalruntime.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SCOMnewbie/PSMSALNet/HEAD/source/lib/runtimes/win-x64/native/msalruntime.dll -------------------------------------------------------------------------------- /source/lib/Microsoft.Identity.Client.Extensions.Msal.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SCOMnewbie/PSMSALNet/HEAD/source/lib/Microsoft.Identity.Client.Extensions.Msal.dll -------------------------------------------------------------------------------- /source/lib/Microsoft.Identity.Client.NativeInterop.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SCOMnewbie/PSMSALNet/HEAD/source/lib/Microsoft.Identity.Client.NativeInterop.dll -------------------------------------------------------------------------------- /source/lib/runtimes/win-arm64/native/msalruntime_arm64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SCOMnewbie/PSMSALNet/HEAD/source/lib/runtimes/win-arm64/native/msalruntime_arm64.dll -------------------------------------------------------------------------------- /source/Examples/managed-identity/script.ps1: -------------------------------------------------------------------------------- 1 | import-module PSMSALNet 2 | 3 | while($true){ 4 | Get-EntraToken -SystemManagedIdentity -Resource GraphAPI 5 | Start-Sleep -Seconds 30 6 | } 7 | -------------------------------------------------------------------------------- /source/PSMSALNet.psm1: -------------------------------------------------------------------------------- 1 | <# 2 | This file is intentionally left empty. It is must be left here for the module 3 | manifest to refer to. It is recreated during the build process. 4 | #> 5 | 6 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "default": true, 3 | "MD029": { 4 | "style": "one" 5 | }, 6 | "MD013": true, 7 | "MD024": false, 8 | "MD034": false, 9 | "no-hard-tabs": true 10 | } 11 | -------------------------------------------------------------------------------- /source/prefix.ps1: -------------------------------------------------------------------------------- 1 | # This is required to expose in a private way the cmdlet Get-WAMToken which is required when we use the -WAMFlow 2 | import-module $(Join-Path $PSScriptRoot 'lib' 'WAMHelper.dll') -ErrorAction Stop 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | output/ 2 | 3 | **.bak 4 | *.local.* 5 | !**/README.md 6 | .kitchen/ 7 | 8 | *.nupkg 9 | *.suo 10 | *.user 11 | *.coverage 12 | .vs 13 | .psproj 14 | .sln 15 | markdownissues.txt 16 | node_modules 17 | package-lock.json 18 | -------------------------------------------------------------------------------- /source/Examples/aks-workloadidentity/sa-aad.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | annotations: 5 | azure.workload.identity/client-id: #<---clientId---> 6 | name: workload-identity-sa 7 | namespace: wip 8 | -------------------------------------------------------------------------------- /.build/custom.build.ps1: -------------------------------------------------------------------------------- 1 | task custom_test { 2 | write-host "test from custom_test task" 3 | } 4 | 5 | task custom_before { 6 | write-host "test from custom_before task" 7 | } 8 | 9 | task custom_after { 10 | write-host "test from custom_after task" 11 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/General.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: General question or documentation update 3 | about: If you have a general question or documentation update suggestion around the resource module. 4 | --- 5 | 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: "Virtual PowerShell User Group #DSC channel" 4 | url: https://dsccommunity.org/community/contact/ 5 | about: "To talk to the community and maintainers of DSC Community, please visit the #DSC channel." 6 | 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Please check out common DSC Community [contributing guidelines](https://dsccommunity.org/guidelines/contributing). 4 | 5 | ## Running the Tests 6 | 7 | If want to know how to run this module's tests you can look at the [Testing Guidelines](https://dsccommunity.org/guidelines/testing-guidelines/#running-tests) 8 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Needed for publishing of examples, build worker defaults to core.autocrlf=input. 2 | * text eol=autocrlf 3 | 4 | *.mof text eol=crlf 5 | *.sh text eol=lf 6 | *.svg eol=lf 7 | 8 | # Ensure any exe files are treated as binary 9 | *.exe binary 10 | *.jpg binary 11 | *.xl* binary 12 | *.pfx binary 13 | *.png binary 14 | *.dll binary 15 | *.so binary 16 | -------------------------------------------------------------------------------- /source/Examples/managed-identity/deploy.ps1: -------------------------------------------------------------------------------- 1 | $MyRG = '' 2 | $ACRName = '' 3 | 4 | az acr update -n $ACRName --admin-enabled true 5 | $acrPassword = az acr credential show -n $ACRName -g $MyRG | ConvertFrom-Json -Depth 99 | % passwords | select -ExpandProperty value -f 1 6 | $Token = az acr login -n $ACRName --expose-token 7 | az acr build --image "aci:latest" -g $MyRG --registry $ACRName "$($PWD.path)" --file "Dockerfile" 8 | -------------------------------------------------------------------------------- /source/Examples/aks-workloadidentity/pod-deploy-pwsh.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: demoworkloadidentity 5 | namespace: wip 6 | labels: 7 | azure.workload.identity/use: "true" 8 | spec: 9 | serviceAccountName: workload-identity-sa 10 | containers: 11 | - name: msalwipdemo 12 | image: .azurecr.io/msalwipdemo:latest 13 | imagePullPolicy: Always 14 | imagePullSecrets: 15 | - name: acr-secret 16 | -------------------------------------------------------------------------------- /source/Examples/managed-identity/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/powershell:lts-ubuntu-22.04 2 | 3 | #Ubuntu require a -m to create the home 4 | # RUN useradd -u 1001 nonroot -m 5 | 6 | WORKDIR /usr/src/script 7 | 8 | RUN pwsh -c Install-Module -Name PSMSALNet -Repository PSGallery -RequiredVersion '0.0.6' -Force -AcceptLicense -Verbose -Scope Allusers 9 | 10 | COPY script.ps1 /usr/src/script 11 | 12 | #USER 1001 13 | 14 | CMD ["pwsh","-File","./script.ps1","-Verbose"] 15 | -------------------------------------------------------------------------------- /source/Examples/aks-workloadidentity/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/powershell:lts-ubuntu-22.04 2 | 3 | #Ubuntu require a -m to create the home 4 | # RUN useradd -u 1001 nonroot -m 5 | 6 | WORKDIR /usr/src/script 7 | 8 | RUN pwsh -c Install-Module -Name PSMSALNet -Repository PSGallery -RequiredVersion '0.0.6' -Force -AcceptLicense -Verbose -Scope Allusers 9 | 10 | COPY script.ps1 /usr/src/script 11 | 12 | #USER 1001 13 | 14 | CMD ["pwsh","-File","./script.ps1","-Verbose"] 15 | -------------------------------------------------------------------------------- /tests/Unit/Public/Get-EntraToken.Tests.ps1: -------------------------------------------------------------------------------- 1 | $ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path 2 | $ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{ 3 | ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and 4 | $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } ) 5 | }).BaseName 6 | 7 | 8 | Import-Module $ProjectName 9 | 10 | Describe Get-EntraToken{ 11 | Context 'Public Client from ClientApplication' { 12 | 13 | It 'Unit tests to implement' { 14 | $true | Should -BeTrue 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/Unit/Public/Get-KVCertificateWithPublicKey.Tests.ps1: -------------------------------------------------------------------------------- 1 | $ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path 2 | $ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{ 3 | ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and 4 | $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } ) 5 | }).BaseName 6 | 7 | 8 | Import-Module $ProjectName 9 | 10 | InModuleScope $ProjectName { 11 | Describe Get-KVCertificateWithPublicKey { 12 | Context 'Default' { 13 | 14 | It 'Unit tests to implement' { 15 | $true | Should -BeTrue 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/Unit/Public/Get-KVCertificateWithPrivateKey.Tests.ps1: -------------------------------------------------------------------------------- 1 | $ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path 2 | $ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{ 3 | ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and 4 | $(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } ) 5 | }).BaseName 6 | 7 | 8 | Import-Module $ProjectName 9 | 10 | InModuleScope $ProjectName { 11 | Describe Get-KVCertificateWithPrivateKey { 12 | Context 'Default' { 13 | 14 | It 'Unit tests to implement' { 15 | $true | Should -BeTrue 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /source/en-US/about_PSMSALNet.help.txt: -------------------------------------------------------------------------------- 1 | TOPIC 2 | about_PSMSALNet 3 | 4 | SHORT DESCRIPTION 5 | Module used to interact with Microsoft Authentication library 6 | 7 | LONG DESCRIPTION 8 | Module used to interact with Microsoft Authentication library 9 | 10 | EXAMPLES 11 | PS C:\> {{ add examples here }} 12 | 13 | NOTE: 14 | Thank you to all those who contributed to this module, by writing code, sharing opinions, and provided feedback. 15 | 16 | TROUBLESHOOTING NOTE: 17 | Look out on the Github repository for issues and new releases. 18 | 19 | SEE ALSO 20 | - {{ Please add Project URI such as github }}} 21 | 22 | KEYWORDS 23 | {{ Add comma separated keywords here }} 24 | 25 | -------------------------------------------------------------------------------- /RequiredModules.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | PSDependOptions = @{ 3 | AddToPath = $true 4 | Target = 'output\RequiredModules' 5 | Parameters = @{ 6 | Repository = 'PSGallery' 7 | } 8 | } 9 | 10 | InvokeBuild = 'latest' 11 | PSScriptAnalyzer = 'latest' 12 | Pester = 'latest' 13 | ModuleBuilder = 'latest' 14 | ChangelogManagement = 'latest' 15 | Sampler = 'latest' 16 | 'Sampler.GitHubTasks' = 'latest' 17 | MarkdownLinkCheck = 'latest' 18 | 'DscResource.Common' = 'latest' 19 | 'DscResource.Test' = 'latest' 20 | 'DscResource.AnalyzerRules' = 'latest' 21 | xDscResourceDesigner = 'latest' 22 | 'DscResource.DocGenerator' = 'latest' 23 | PlatyPs = 'latest' 24 | } 25 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | require_ci_to_pass: no 3 | # master should be the baseline for reporting 4 | branch: main 5 | 6 | comment: 7 | layout: "reach, diff, flags, files" 8 | behavior: default 9 | 10 | coverage: 11 | range: 50..80 12 | round: down 13 | precision: 0 14 | 15 | status: 16 | project: 17 | default: 18 | # Set the overall project code coverage requirement to 70% 19 | target: 70 20 | patch: 21 | default: 22 | # Set the pull request requirement to not regress overall coverage by more than 5% 23 | # and let codecov.io set the goal for the code changed in the patch. 24 | target: auto 25 | threshold: 5 26 | 27 | # Use this if there are paths that should not be part of the code coverage, for 28 | # example a deprecated function where tests has been removed. 29 | #ignore: 30 | # - 'source/Public/Get-Deprecated.ps1' 31 | 32 | -------------------------------------------------------------------------------- /GitVersion.yml: -------------------------------------------------------------------------------- 1 | mode: ContinuousDelivery 2 | next-version: 0.0.1 3 | major-version-bump-message: '(breaking\schange|breaking|major)\b' 4 | minor-version-bump-message: '(adds?|features?|minor)\b' 5 | patch-version-bump-message: '\s?(fix|patch)' 6 | no-bump-message: '\+semver:\s?(none|skip)' 7 | assembly-informational-format: '{NuGetVersionV2}+Sha.{Sha}.Date.{CommitDate}' 8 | branches: 9 | master: 10 | tag: preview 11 | regex: ^main$ 12 | pull-request: 13 | tag: PR 14 | feature: 15 | tag: useBranchName 16 | increment: Minor 17 | regex: f(eature(s)?)?[\/-] 18 | source-branches: ['master'] 19 | hotfix: 20 | tag: fix 21 | increment: Patch 22 | regex: (hot)?fix(es)?[\/-] 23 | source-branches: ['master'] 24 | 25 | ignore: 26 | sha: [] 27 | merge-message-formats: {} 28 | 29 | 30 | # feature: 31 | # tag: useBranchName 32 | # increment: Minor 33 | # regex: f(eature(s)?)?[/-] 34 | # source-branches: ['master'] 35 | # hotfix: 36 | # tag: fix 37 | # increment: Patch 38 | # regex: (hot)?fix(es)?[/-] 39 | # source-branches: ['master'] 40 | 41 | -------------------------------------------------------------------------------- /tests/helpers/secret.enc: -------------------------------------------------------------------------------- 1 | -----BEGIN CMS----- 2 | MIIC5wYJKoZIhvcNAQcDoIIC2DCCAtQCAQAxggJPMIICSwIBADAzMB8xHTAbBgNVBAMMFGNlcnQw 3 | MkBjb21uZXdiaWUuY29tAhAuFx+wADpOk0HW1kVPUH/oMA0GCSqGSIb3DQEBAQUABIICAHEMxwAW 4 | Fjs1qwLCyHzZ3V1FlAEUkyKn8zHrKxfQ5ZEmbVvQC/B+22QH0wZH3290izHZ6roqUAadQN9MPlb9 5 | 3UnZR6r5aw5icozEaEBHV/TH9xGrkarVEhssUiLOSNY8X4A0apbZjJ105L15tpgtuCBKSUevzyCN 6 | DehHbCIJfJMWWzcdq8ZQ7wkl3zszd10nzOKO6p7zSx8I5EDFLuFOa5wp8V2GZJKDFBpPwQQ2Rga8 7 | /FDxVCELRWQC2XEvNeXfUcJve23X9BQ5fUJIMpNF9/dThONZjOOLnYhEOnMOHMfWmB2CDIMBSFmk 8 | NMLJ4zvyO5iCxeyi1mgpBiSAE5Popp3bG/BER1rnzf/3NRkGWGmq5c64Nvuajh6SMmppuH+GVlum 9 | 8catTYr1VJ3cndxzMZaiboaDG5LahvQDa3ANTWkTmbDpvcsgb2lhghYkLmnh+xoE6vOLRTqJQikb 10 | G3TQDHnNegG4RmkV/2OL2KHZvviFoB57y7Pdw6dBZ40Vmad3IiyarbNzZXoS7v0KgXyJoh7sGrNY 11 | CMy6G72oZUvvLI8Jy8vaHJ1yalVg6AA6p4TNAskpa/GeKmNHMnfG3zQ7+lmxvwzslRQb+ea/zGYs 12 | CbVXhIVAtfNhTCikvg9S70OGb68EWsFoIzexNKzuEPn/ojz9OlEqnweK128GitI1T2rUMHwGCSqG 13 | SIb3DQEHATAdBglghkgBZQMEASoEEOyhrRWqMSQdJhgzh2qrvBGAULNG18tfp4x0Os5uJc/DJXKR 14 | 6Q1HDEMXGzxrwZQPxohVloHPJcRFhh03oU8vU40bXFO3tgetg4/D5UYAwgqtNUjBNgSHQqTD/ic1 15 | QkMtLCgD 16 | -----END CMS----- -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 SCOMnewbie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /source/Examples/aks-workloadidentity/script.ps1: -------------------------------------------------------------------------------- 1 | import-module PSMSALNet 2 | 3 | while ($true) { 4 | 5 | Write-Host '###############################' 6 | Write-Host 'Working on workload identity demo' 7 | Write-Host '###############################' 8 | Write-Host '' 9 | 10 | #Read Kubernetes Service account token from mounted volume 11 | Write-Host " Read Kubernetes projected service account token" 12 | $KubeSaToken = Get-Content -Path '/var/run/secrets/azure/tokens/azure-identity-token' 13 | if ($null -eq $KubeSaToken) { 14 | Throw 'Kubernetes Service Account token is not exposed as volume' 15 | } 16 | else { 17 | Write-Host ' Kubernetes token exposed through volume:' 18 | Write-host " $KubeSaToken" 19 | } 20 | 21 | Write-Host '' 22 | Write-Host " Let's exchange this Kubernetes token to Azure AD" 23 | Write-Host '' 24 | 25 | # Contact Entra with federated credential 26 | Get-EntraToken -FederatedCredentialFlowWithAssertion -UserAssertion $KubeSaToken -ClientId $([Environment]::GetEnvironmentVariable('AZURE_CLIENT_ID')) -TenantId $([Environment]::GetEnvironmentVariable('AZURE_TENANT_ID')) -Resource GraphAPI 27 | 28 | Start-Sleep -Seconds 10 29 | } 30 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ## Security 2 | 3 | We take the security of our modules seriously, which includes all source code repositories managed through our GitHub organization. 4 | 5 | If you believe you have found a security vulnerability in any of our repository, please report it to us as described below. 6 | 7 | ## Reporting Security Issues 8 | 9 | **Please do not report security vulnerabilities through public GitHub issues.** 10 | 11 | Instead, please report them to one or several maintainers of the repository. 12 | The easiest way to do so is to send us a direct message via twitter or find us 13 | on some other social platform. 14 | 15 | You should receive a response within 48 hours. If for some reason you do not, please follow up by other means or to other contributors. 16 | 17 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 18 | 19 | * Type of issue 20 | * Full paths of source file(s) related to the manifestation of the issue 21 | * The location of the affected source code (tag/branch/commit or direct URL) 22 | * Any special configuration required to reproduce the issue 23 | * Step-by-step instructions to reproduce the issue 24 | * Proof-of-concept or exploit code (if possible) 25 | * Impact of the issue, including how an attacker might exploit the issue 26 | 27 | This information will help us triage your report more quickly. 28 | 29 | ## Preferred Languages 30 | 31 | We prefer all communications to be in English. 32 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "powershell.codeFormatting.openBraceOnSameLine": false, 3 | "powershell.codeFormatting.newLineAfterOpenBrace": true, 4 | "powershell.codeFormatting.newLineAfterCloseBrace": true, 5 | "powershell.codeFormatting.whitespaceBeforeOpenBrace": true, 6 | "powershell.codeFormatting.whitespaceBeforeOpenParen": true, 7 | "powershell.codeFormatting.whitespaceAroundOperator": true, 8 | "powershell.codeFormatting.whitespaceAfterSeparator": true, 9 | "powershell.codeFormatting.ignoreOneLineBlock": false, 10 | "powershell.codeFormatting.pipelineIndentationStyle": "IncreaseIndentationAfterEveryPipeline", 11 | "powershell.codeFormatting.preset": "Custom", 12 | "powershell.codeFormatting.alignPropertyValuePairs": true, 13 | "powershell.developer.bundledModulesPath": "${cwd}/output/RequiredModules", 14 | "powershell.scriptAnalysis.settingsPath": ".vscode\\analyzersettings.psd1", 15 | "powershell.scriptAnalysis.enable": true, 16 | "files.trimTrailingWhitespace": true, 17 | "files.trimFinalNewlines": true, 18 | "files.insertFinalNewline": true, 19 | "files.associations": { 20 | "*.ps1xml": "xml" 21 | }, 22 | "cSpell.words": [ 23 | "COMPANYNAME", 24 | "ICONURI", 25 | "LICENSEURI", 26 | "PROJECTURI", 27 | "RELEASENOTES", 28 | "buildhelpers", 29 | "endregion", 30 | "gitversion", 31 | "icontains", 32 | "keepachangelog", 33 | "notin", 34 | "pscmdlet", 35 | "steppable" 36 | ], 37 | "[markdown]": { 38 | "files.trimTrailingWhitespace": false, 39 | "files.encoding": "utf8" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /.vscode/analyzersettings.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | CustomRulePath = '.\output\RequiredModules\DscResource.AnalyzerRules' 3 | includeDefaultRules = $true 4 | IncludeRules = @( 5 | # DSC Resource Kit style guideline rules. 6 | 'PSAvoidDefaultValueForMandatoryParameter', 7 | 'PSAvoidDefaultValueSwitchParameter', 8 | 'PSAvoidInvokingEmptyMembers', 9 | 'PSAvoidNullOrEmptyHelpMessageAttribute', 10 | 'PSAvoidUsingCmdletAliases', 11 | 'PSAvoidUsingComputerNameHardcoded', 12 | 'PSAvoidUsingDeprecatedManifestFields', 13 | 'PSAvoidUsingEmptyCatchBlock', 14 | 'PSAvoidUsingInvokeExpression', 15 | 'PSAvoidUsingPositionalParameters', 16 | 'PSAvoidShouldContinueWithoutForce', 17 | 'PSAvoidUsingWMICmdlet', 18 | 'PSAvoidUsingWriteHost', 19 | 'PSDSCReturnCorrectTypesForDSCFunctions', 20 | 'PSDSCStandardDSCFunctionsInResource', 21 | 'PSDSCUseIdenticalMandatoryParametersForDSC', 22 | 'PSDSCUseIdenticalParametersForDSC', 23 | 'PSMisleadingBacktick', 24 | 'PSMissingModuleManifestField', 25 | 'PSPossibleIncorrectComparisonWithNull', 26 | 'PSProvideCommentHelp', 27 | 'PSReservedCmdletChar', 28 | 'PSReservedParams', 29 | 'PSUseApprovedVerbs', 30 | 'PSUseCmdletCorrectly', 31 | 'PSUseOutputTypeCorrectly', 32 | 'PSAvoidGlobalVars', 33 | 'PSAvoidUsingConvertToSecureStringWithPlainText', 34 | 'PSAvoidUsingPlainTextForPassword', 35 | 'PSAvoidUsingUsernameAndPasswordParams', 36 | 'PSDSCUseVerboseMessageInDSCResource', 37 | 'PSShouldProcess', 38 | 'PSUseDeclaredVarsMoreThanAssignments', 39 | 'PSUsePSCredentialType', 40 | 41 | 'Measure-*' 42 | ) 43 | 44 | } 45 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Resource_proposal.yml: -------------------------------------------------------------------------------- 1 | name: New resource proposal 2 | description: If you have a new resource proposal that you think should be added to this resource module. 3 | title: "NewResourceName: New resource proposal" 4 | labels: [] 5 | assignees: [] 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | Please replace `NewResourceName` in the issue title (above) with your proposed resource name. 11 | 12 | Thank you for contributing and making this resource module better! 13 | - type: textarea 14 | id: description 15 | attributes: 16 | label: Resource proposal 17 | description: Provide information how this resource will/should work and how it will help users. 18 | validations: 19 | required: true 20 | - type: textarea 21 | id: proposedProperties 22 | attributes: 23 | label: Proposed properties 24 | description: | 25 | List all the proposed properties that the resource should have (key, required, write, and/or read). For each property provide a detailed description, the data type, if a default value should be used, and if the property is limited to a set of values. 26 | value: | 27 | Property | Type qualifier | Data type | Description | Default value | Allowed values 28 | --- | --- | --- | --- | --- | --- 29 | PropertyName | Key | String | Detailed description | None | None 30 | validations: 31 | required: true 32 | - type: textarea 33 | id: considerations 34 | attributes: 35 | label: Special considerations or limitations 36 | description: | 37 | Provide any considerations or limitations you can think of that a contributor should take in account when coding the proposed resource, and or what limitations a user will encounter or should consider when using the proposed resource. 38 | validations: 39 | required: true 40 | -------------------------------------------------------------------------------- /tests/helpers/scomnewbie.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFDzCCAvegAwIBAgIQLhcfsAA6TpNB1tZFT1B/6DANBgkqhkiG9w0BAQsFADAf 3 | MR0wGwYDVQQDDBRjZXJ0MDJAY29tbmV3YmllLmNvbTAeFw0yMjA5MzAxODM5MzFa 4 | Fw0yMzA5MzAxODU5MzFaMB8xHTAbBgNVBAMMFGNlcnQwMkBjb21uZXdiaWUuY29t 5 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp/omW5nChxaBXWAcyhU9 6 | QIPKr+gLAjv7LnNqnRo25nMq/z01PV50ZoLgKhIahjJhtJwUokls5K/GIl14BWfW 7 | Izj6HDpHycgks6TYQ9yl7qCpUZ33q8gcKxusNmAIjA6/Ap18rsxrYhi3MbNTr48p 8 | uNog4ebxnUq2TFXrzGdHCQx72YbU5AQ4jtfvw4OjV6Et8FCBk43eOD64Y/kqWY1z 9 | ttxyxGgbRNECSPEYyUJLjSX+SF8nPRrUe77VeDdrC+BPB+lcUSzdkT+g2pT7ksQd 10 | TOsnhb3wmJXVKJx0EWfkXM+8rGjmpWC+OS+TPvIz8JfopR20YAyuSbjEy3YUK8Af 11 | YDrVgFWB0OfAAx8+F5cVoziqZbXCoc363lT4jdXiCpkj+tbABsN+RCkrUCTrurqN 12 | VqZFRycNz2VQ8fdnrqI+09ahijwSxXRavntzKbE/nj68dMuSAt4iyCyiYMsVGMn3 13 | zXlt0wcQtyChJ3hqBG1OPN86bBIJHBOuD+RtCAqitvZ2JZZKx5gvH/HA5+vXNqFk 14 | Z3s4Ao3Dh5X5yJ8s4kRk9aUcD9Ljkot5TCPUvfd2MjmBZ7RZL+r0lDlBnfrpjz4M 15 | 4dVTY8SdxS5vUysdRwkNtHhdmRtbr8GMYJXgmWSzaeNW7ljPJuUThAtRxpC5fqvq 16 | XRDfuIU2YiMs3t+u4C0+QkUCAwEAAaNHMEUwDgYDVR0PAQH/BAQDAgQQMBQGA1Ud 17 | JQQNMAsGCSsGAQQBgjdQATAdBgNVHQ4EFgQU7pu5z0afU43/pNp+Za+CX8dEQ7Qw 18 | DQYJKoZIhvcNAQELBQADggIBABOlODhgYs50lnrX/lw6IW80ElVudVRZEmLOzNlO 19 | b8iP89qnawVyC5TVu9kvfOWYe9G2FwSSFTUJCggecSDisqTxz1VlKt4UqaOAJ8Fl 20 | D0odjEeB92TZK/NDwvBEUqkThlM/B0o9FhK/r/v437SBGJMkzJnEIgMYogYel/mV 21 | bT1/uDqvCu7yRIb/xrPk0wyblc1vBwDkvZ1wnfWY7ME8NmQ8qQ71y7o/GZbTh9VG 22 | ZIolbrBIyUOH1u+Xu2kSOZtIBX6ujQ688jUumdUSN4oGz0yJjb2j/t6j6CGhob2v 23 | qZ3EJlE9McBI0X744M/bk0/bAwCogE7wRDvswr62x8zcrjqogHa+EQrZ3joG/Ijq 24 | QL2b8Vlt7LEhMVD3lB7xZq17NVdq0RQMscUYB5f0968mv6lU+3t5ML1iM18S5OY9 25 | /yca/xJl1c/9jimzQNVCihsfyeZpkoXj2g4oiVdBQ9RE4l0H6mymeaTReSjJ/q3z 26 | caO5smf1eCOrAkLGb+c+YWTbp2JwWWLCLKs1IkH9xCNAVhlNBI/IGFAwYNCMIE9f 27 | iyw3xPnOu9mw431UZpiQmmYx7IDID5C1TOGrUI5bSTlF6JZx8YPS/oKbZJ0cey5W 28 | JLu4SfUQ/K1mmyx9ngLwKyA73zu3t8vPLSB2Fk2Bl81n9ejYmYwhq21awoDukukr 29 | /FEs 30 | -----END CERTIFICATE----- 31 | -------------------------------------------------------------------------------- /tests/helpers/readme.txt: -------------------------------------------------------------------------------- 1 | Pfx password = "exportpassword" 2 | Passphrase = "test" 3 | 4 | ------------------------------------------------------------------------------------------------- 5 | 6 | https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.security/about/about_certificate_provider?view=powershell-7.2 7 | Types exposed by this provider 8 | The Certificate drive exposes the following types. 9 | 10 | Store locations (Microsoft.PowerShell.Commands.X509StoreLocation), which are high-level containers that group the certificates for the current user and for all users. Each system has a CurrentUser and LocalMachine (all users) store location. 11 | Certificates stores (System.Security.Cryptography.X509Certificates.X509Store), which are physical stores in which certificates are saved and managed. 12 | X.509 System.Security.Cryptography.X509Certificates.X509Certificate2 certificates, each of which represent an X.509 certificate on the computer. Certificates are identified by their thumbprints. 13 | 14 | 15 | 16 | Decrypt 17 | 18 | pfx 19 | pfx + password 20 | $PrivCert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($(Get-Item -Path .\scomnewbie.pfx),"exportpassword") 21 | Unprotect-CmsMessage -Path .\secret.enc -To $PrivCert 22 | pem (-nodes remove the privatekey need) 23 | openssl pkcs12 -in ./scomnewbie.pfx -out ./scomnewbie.pem -nodes # WARNING No more password anymore 24 | $PrivCert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::CreateFromPemFile($(Get-Item -Path ./scomnewbie.pem)) 25 | pem + decrypted private key 26 | openssl pkcs12 -in ./scomnewbie.pfx -out ./scomnewbie2.pem # Privatekey will be encrypted + no -nodes means passphrase required 27 | openssl rsa -in ./scomnewbie2.pem -out privatekey_rsa.key #Enter passphrase 28 | $PrivCert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::CreateFromPemFile($(Get-Item -Path ./scomnewbie.pem),$(Get-Item -Path ./privatekey_rsa.key)) 29 | -------------------------------------------------------------------------------- /source/Public/Get-KVCertificateWithPublicKey.ps1: -------------------------------------------------------------------------------- 1 | function Get-KVCertificateWithPublicKey 2 | { 3 | <# 4 | .SYNOPSIS 5 | This is a function to download certificate information from Azure KeyVault. 6 | 7 | .DESCRIPTION 8 | This is a function to download certificate information from Azure KeyVault. 9 | 10 | .EXAMPLE 11 | TODO: Write examples 12 | 13 | .PARAMETER KeyVaultCertificatePath 14 | The KeyVaultCertificatePath parameter is the path of the Keyvault certificate. 15 | 16 | .PARAMETER AccessToken 17 | The AccessToken parameter is the JWT you have to provide to do the action. 18 | 19 | .PARAMETER APIVersion 20 | The APIVersion parameter is the version of the Keyvault API. 21 | 22 | #> 23 | [CmdletBinding()] 24 | param( 25 | [Parameter(Mandatory)] 26 | [string]$KeyVaultCertificatePath, #https://ubuntukv415745.vault.azure.net/certificates/test/5d69153b75214245ab72fa21b9c06bfb 27 | [Parameter(Mandatory)] 28 | [string]$AccessToken, 29 | [string]$APIVersion = '7.3' 30 | ) 31 | 32 | # Force TLS 1.2. 33 | Write-Verbose "[$((Get-Date).TimeofDay)] Starting $($myinvocation.mycommand)" 34 | Write-Verbose "[$((Get-Date).TimeofDay)] Get-KVCertificateWithPublicKey - Force TLS 1.2" 35 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 36 | 37 | if ($AccessToken -notlike 'Bearer*') 38 | { 39 | $AccessToken = "Bearer $($AccessToken)" 40 | } 41 | 42 | $Headers = @{ 43 | 'Content-Type' = 'application/json' 44 | 'Authorization' = $AccessToken 45 | } 46 | 47 | $CertURL = "$($KeyVaultCertificatePath)?api-version=$($APIVersion)" 48 | #$certURL = "https://ubuntukv415745.vault.azure.net/certificates/test/5d69153b75214245ab72fa21b9c06bfb?api-version=$APIVersion" 49 | 50 | Invoke-RestMethod -Uri $certURL -Headers $Headers 51 | } 52 | -------------------------------------------------------------------------------- /Resolve-Dependency.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | #PSDependTarget = './output/modules' 3 | #Proxy = '' 4 | #ProxyCredential = '$MyCredentialVariable' #TODO: find a way to support credentials in build (resolve variable) 5 | 6 | Gallery = 'PSGallery' 7 | 8 | # To use a private nuget repository change the following to your own feed. The locations must be a Nuget v2 feed due 9 | # to limitation in PowerShellGet v2.x. Example below is for a Azure DevOps Server project-scoped feed. While resolving 10 | # dependencies it will be registered as a trusted repository with the name specified in the property 'Gallery' above, 11 | # unless property 'Name' is provided in the hashtable below, if so it will override the property 'Gallery' above. The 12 | # registered repository will be removed when dependencies has been resolved, unless it was already registered to begin 13 | # with. If repository is registered already but with different URL:s the repository will be re-registered and reverted 14 | # after dependencies has been resolved. Currently only Windows integrated security works with private Nuget v2 feeds 15 | # (or if it is a public feed with no security), it is not possible yet to securely provide other credentials for the feed. 16 | #RegisterGallery = @{ 17 | # #Name = 'MyPrivateFeedName' 18 | # GallerySourceLocation = 'https://azdoserver.company.local///_packaging//nuget/v2' 19 | # GalleryPublishLocation = 'https://azdoserver.company.local///_packaging//nuget/v2' 20 | # GalleryScriptSourceLocation = 'https://azdoserver.company.local///_packaging//nuget/v2' 21 | # GalleryScriptPublishLocation = 'https://azdoserver.company.local///_packaging//nuget/v2' 22 | # #InstallationPolicy = 'Trusted' 23 | #} 24 | 25 | #AllowOldPowerShellGetModule = $true 26 | #MinimumPSDependVersion = '0.3.0' 27 | AllowPrerelease = $false 28 | WithYAML = $true # Will also bootstrap PowerShell-Yaml to read other config files 29 | } 30 | 31 | -------------------------------------------------------------------------------- /source/Public/Get-KVCertificateWithPrivateKey.ps1: -------------------------------------------------------------------------------- 1 | function Get-KVCertificateWithPrivateKey 2 | { 3 | <# 4 | .SYNOPSIS 5 | This is a function to download certificate information from Azure KeyVault and export the private key as well. 6 | 7 | .DESCRIPTION 8 | This is a function to download certificate information from Azure KeyVault and export the private key as well. 9 | 10 | .EXAMPLE 11 | TODO: Write examples 12 | 13 | .PARAMETER KeyVaultCertificatePath 14 | The KeyVaultCertificatePath parameter is the path of the Keyvault certificate. 15 | 16 | .PARAMETER AccessToken 17 | The AccessToken parameter is the JWT you have to provide to do the action. 18 | 19 | .PARAMETER APIVersion 20 | The APIVersion parameter is the version of the Keyvault API. 21 | 22 | #> 23 | [CmdletBinding()] 24 | param( 25 | [Parameter(Mandatory)] 26 | [string]$KeyVaultCertificatePath, #https://ubuntukv415745.vault.azure.net/certificates/test/5d69153b75214245ab72fa21b9c06bfb 27 | [Parameter(Mandatory)] 28 | [string]$AccessToken, 29 | [string]$APIVersion = '7.3' 30 | ) 31 | 32 | # Force TLS 1.2. 33 | Write-Verbose "[$((Get-Date).TimeofDay)] Starting $($myinvocation.mycommand)" 34 | Write-Verbose "[$((Get-Date).TimeofDay)] Get-KVCertificateWithPrivateKey - Force TLS 1.2" 35 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 36 | 37 | if ($AccessToken -notlike 'Bearer*') 38 | { 39 | $AccessToken = "Bearer $($AccessToken)" 40 | } 41 | 42 | $Headers = @{ 43 | 'Content-Type' = 'application/json' 44 | 'Authorization' = $AccessToken 45 | } 46 | 47 | $splat = @{ 48 | 'KeyVaultCertificatePath' = $KeyVaultCertificatePath 49 | 'AccessToken' = $AccessToken 50 | 'APIVersion' = $APIVersion 51 | } 52 | 53 | $CertInfo = Get-KVCertificateWithPublicKey @splat 54 | 55 | if ($null -eq $CertInfo.sid) 56 | { 57 | throw 'Unable to find private key information' 58 | } 59 | 60 | #Now we have certificate information let's find private key info 61 | Invoke-RestMethod -Uri "$($CertInfo.sid)?api-version=$($APIVersion)" -Headers $Headers 62 | } 63 | -------------------------------------------------------------------------------- /tests/Unit/Public/ConvertFrom-Jwt.Tests.ps1: -------------------------------------------------------------------------------- 1 | $ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path 2 | $ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{ 3 | ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and 4 | $(try 5 | { 6 | Test-ModuleManifest $_.FullName -ErrorAction Stop 7 | } 8 | catch 9 | { 10 | $false 11 | } ) 12 | }).BaseName 13 | 14 | 15 | Import-Module $ProjectName 16 | 17 | InModuleScope $ProjectName { 18 | BeforeAll { 19 | 20 | $BadToken = 'wrongtoken' 21 | $ValidToken = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IkwxS2ZLRklfam5YYndXYzIyeFp4dzFzVUhIMCIsImtpZCI6IkwxS2ZLRklfam5YYndXYzIyeFp4dzFzVUhIMCJ9.eyJhdWQiOiJodHRwczovL21hbmFnZW1lbnQuYXp1cmUuY29tIiwiaXNzIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvOWZjNDgwNDAtYmQ4Yy00ZjNmLWI3YjMtZmYxN2NiZjA0YjIwLyIsImlhdCI6MTcxNTcxMjkwMCwibmJmIjoxNzE1NzEyOTAwLCJleHAiOjE3MTU3MTY4MDAsImFpbyI6IkUyTmdZT0MvWmI1b2UwV3dUSzhxZDgyVm1OQklBQT09IiwiYXBwaWQiOiIxZWM5ZWZlZC04YjVhLTQ4ODMtYThjMC01MTA0Y2MxMTg1MjkiLCJhcHBpZGFjciI6IjEiLCJpZHAiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC85ZmM0ODA0MC1iZDhjLTRmM2YtYjdiMy1mZjE3Y2JmMDRiMjAvIiwiaWR0eXAiOiJhcHAiLCJvaWQiOiJjN2U0NDE1MC0zMDc3LTQ3NjgtOGM0Mi03ZGM5OGRhY2I1YjUiLCJyaCI6IjAuQVZ3QVFJREVuNHk5UDAtM3NfOFh5X0JMSUVaSWYza0F1dGRQdWtQYXdmajJNQk5jQUFBLiIsInN1YiI6ImM3ZTQ0MTUwLTMwNzctNDc2OC04YzQyLTdkYzk4ZGFjYjViNSIsInRpZCI6IjlmYzQ4MDQwLWJkOGMtNGYzZi1iN2IzLWZmMTdjYmYwNGIyMCIsInV0aSI6IjVTcThxMXJhemtHdFdZT2t6Q1k5QUEiLCJ2ZXIiOiIxLjAiLCJ4bXNfdGNkdCI6MTU5MzYwMjgyN30.RdW8_Hsd3sAursvPXdK2EuXgh4nEeDzbV43k9o7dkpDiKvtZsc9fvfTcREYedyM_QVo8_S0fa1quPFrwWyn3xfcbQ1evviN1c1N2GSTH0S7ZCcgQlr3MOy-9Yu_inmHWnJFlzsqg-GBjTIGGLmuMz9rHrFpC0f7XRkHfnSULSTkdS3wOyuLvxeEhnZerL3zu9YlHMI2L9XEjJzKWPfAqxlzw2P442FYTwp_TDJwg2A_yzH3fIFCdknBdsSSZSVdJA7ly2VEXgyi8TpuCiYyxcfq0sHeQGqUxTFzUFO3qsZy0LE63AdBOj1foRrn5MB8vEX16StL-mWbZvJlKUzdpEQ' 22 | } 23 | 24 | Describe ConvertFrom-Jwt { 25 | Context 'With wrong value' { 26 | 27 | It 'Should return invalid token' { 28 | $v = ConvertFrom-Jwt -Token $BadToken 29 | $v | Should -Be -ExpectedValue 'Invalid token' 30 | } 31 | } 32 | 33 | Context 'With good value' { 34 | It 'Should not throw with good token' { 35 | { ConvertFrom-Jwt -Token $ValidToken} | Should -not -Throw 36 | } 37 | 38 | It 'Should contain kid property in header' { 39 | $Kid = (ConvertFrom-Jwt -Token $ValidToken).Tokenheader.kid 40 | $Kid | Should -BeExactly 'L1KfKFI_jnXbwWc22xZxw1sUHH0' 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Pull Request 2 | 3 | 16 | 17 | ## Pull Request (PR) description 18 | 19 | 40 | 41 | ## Task list 42 | 43 | 51 | 52 | - [ ] The PR represents a single logical change. i.e. Cosmetic updates should go in different PRs. 53 | - [ ] Added an entry under the Unreleased section of in the CHANGELOG.md as per [format](https://keepachangelog.com/en/1.0.0/). 54 | - [ ] Local clean build passes without issue or fail tests (`build.ps1 -ResolveDependency`). 55 | - [ ] Resource documentation added/updated in README.md. 56 | - [ ] Resource parameter descriptions added/updated in README.md, schema.mof 57 | and comment-based help. 58 | - [ ] Comment-based help added/updated. 59 | - [ ] Localization strings added/updated in all localization files as appropriate. 60 | - [ ] Examples appropriately added/updated. 61 | - [ ] Unit tests added/updated. See [DSC Resource Testing Guidelines](https://github.com/PowerShell/DscResources/blob/master/TestsGuidelines.md). 62 | - [ ] Integration tests added/updated (where possible). See [DSC Resource Testing Guidelines](https://github.com/PowerShell/DscResources/blob/master/TestsGuidelines.md). 63 | - [ ] New/changed code adheres to [DSC Resource Style Guidelines](https://github.com/PowerShell/DscResources/blob/master/StyleGuidelines.md) and [Best Practices](https://github.com/PowerShell/DscResources/blob/master/BestPractices.md). 64 | -------------------------------------------------------------------------------- /source/Examples/aks-workloadidentity/demo.ps1: -------------------------------------------------------------------------------- 1 | ################# 2 | # Demo Workload identity where we use credential federation 3 | ################# 4 | 5 | Create first your Kubernetes (managed or not) 6 | 7 | <# 8 | https://azure.github.io/azure-workload-identity/docs/introduction.html 9 | #> 10 | 11 | <# 12 | Explain the Helm addition at the beginning 13 | https://azure.github.io/azure-workload-identity/docs/installation/mutating-admission-webhook.html#helm-3-recommended 14 | #> 15 | 16 | Create your dedicated app registration that will represent your pod(s) 17 | 18 | <# 19 | App reg config: 20 | No authentication 21 | federated credential 22 | kubernetes 23 | 24 | # Output the OIDC issuer URL 25 | az aks show --resource-group --name --query "oidcIssuerProfile.issuerUrl" -otsv 26 | namespace 27 | wip 28 | Service account 29 | workload-identity-sa 30 | Audience 31 | api://AzureADTokenExchange 32 | No exposed api 33 | #> 34 | 35 | #IMPORTANT: Add your values here 36 | $RG = '' 37 | $ACRName = '' 38 | $ACRPassword = "" 39 | $KubeClustername = "" 40 | $TenantId = '' 41 | 42 | #Get current cluster info 43 | kubectl cluster-info dump -o json 44 | 45 | # Takes time to be up and running (explain later) 46 | helm repo add azure-workload-identity https://azure.github.io/azure-workload-identity/charts 47 | helm repo update 48 | helm install workload-identity-webhook azure-workload-identity/workload-identity-webhook ` 49 | --namespace azure-workload-identity-system ` 50 | --create-namespace ` 51 | --set azureTenantID=$TenantId 52 | 53 | #Enable the oidc url 54 | az aks update -g $RG -n $KubeClustername --enable-oidc-issuer 55 | 56 | #Show OIDC endpoint url (required in the app registration Cluster issuer URL) 57 | az aks show -n $KubeClustername -g $RG --query "oidcIssuerProfile.issuerUrl" -otsv 58 | 59 | # Create wip namespace 60 | kubectl create namespace wip 61 | 62 | # Create service account with annotation related to the attached Azure AD app c211bd26-259d-4ebc-bdb9-6c8c45a7a88f in this case 63 | kubectl apply -f .\sa-aad.yaml 64 | 65 | #wait for workload identity to start 66 | kubectl get pods -A 67 | 68 | # Build container and put it in acr (done already) 69 | az acr build --image "msalwipdemo:latest" -g $RG --registry $ACRName . --file ..\dockerfile 70 | 71 | #Declare ACR secret in kubernetes 72 | # IMPORTANT: Check password 73 | 74 | kubectl create secret docker-registry acr-secret -n wip --docker-server="$ACRName.azurecr.io" --docker-username=$ACRName --docker-password=$ACRPassword 75 | 76 | # Deploy pod that will use ns, sa, Azure acr with stored secret 77 | kubectl apply -f .\pod-deploy-pwsh.yaml 78 | 79 | # Gt pod status 80 | kubectl get pods -n wip 81 | 82 | # Describe the pod (check injected env variable) 83 | kubectl describe pod demoworkloadidentity -n wip 84 | 85 | # Enjoy logs 86 | kubectl logs demoworkloadidentity -n wip 87 | 88 | #If you want to look insode the container 89 | kubectl exec demoworkloadidentity -n wip -it -- /bin/pwsh 90 | 91 | ls /var/run/secrets/kubernetes.io/serviceaccount/ 92 | ls /var/run/secrets/azure/tokens/azure-identity-token 93 | 94 | #Delete pod 95 | kubectl delete pod demoworkloadidentity -n wip 96 | -------------------------------------------------------------------------------- /tests/helpers/privatekey_rsa.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | 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 51 | tjHjm3wQG1KILUF70oBj4hmlIuiNRQ== 52 | -----END PRIVATE KEY----- 53 | -------------------------------------------------------------------------------- /tests/helpers/wrongprivatekey_rsa.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | 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 51 | tjHjm3wQG1KILUF70oBj4hmlIuiNRQ== 52 | -----END PRIVATE KEY----- 53 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Problem_with_resource.yml: -------------------------------------------------------------------------------- 1 | name: Problem with a resource 2 | description: If you have a problem, bug, or enhancement with a resource in this resource module. 3 | labels: [] 4 | assignees: [] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Please prefix the issue title (above) with the resource name, e.g. 'ResourceName: Short description of my issue'! 10 | 11 | Your feedback and support is greatly appreciated, thanks for contributing! 12 | - type: textarea 13 | id: description 14 | attributes: 15 | label: Problem description 16 | description: Details of the scenario you tried and the problem that is occurring. 17 | validations: 18 | required: true 19 | - type: textarea 20 | id: logs 21 | attributes: 22 | label: Verbose logs 23 | description: | 24 | Verbose logs showing the problem. **NOTE! Sensitive information should be obfuscated.** _Will be automatically formatted as plain text._ 25 | placeholder: | 26 | Paste verbose logs here 27 | render: text 28 | validations: 29 | required: true 30 | - type: textarea 31 | id: configuration 32 | attributes: 33 | label: DSC configuration 34 | description: | 35 | The DSC configuration that is used to reproduce the issue (as detailed as possible). **NOTE! Sensitive information should be obfuscated.** _Will be automatically formatted as PowerShell code._ 36 | placeholder: | 37 | Paste DSC configuration here 38 | render: powershell 39 | validations: 40 | required: true 41 | - type: textarea 42 | id: suggestedSolution 43 | attributes: 44 | label: Suggested solution 45 | description: Do you have any suggestions how to solve the issue? 46 | validations: 47 | required: true 48 | - type: textarea 49 | id: targetNodeOS 50 | attributes: 51 | label: Operating system the target node is running 52 | description: | 53 | Please provide as much as possible about the target node, for example edition, version, build, and language. _Will be automatically formatted as plain text._ 54 | 55 | On OS with WMF 5.1 the following command can help get this information: `Get-ComputerInfo -Property @('OsName','OsOperatingSystemSKU','OSArchitecture','WindowsVersion','WindowsBuildLabEx','OsLanguage','OsMuiLanguages')` 56 | placeholder: | 57 | Add operating system information here 58 | render: text 59 | validations: 60 | required: true 61 | - type: textarea 62 | id: targetNodePS 63 | attributes: 64 | label: PowerShell version and build the target node is running 65 | description: | 66 | Please provide the version and build of PowerShell the target node is running. _Will be automatically formatted as plain text._ 67 | 68 | To help with this information, please run this command: `$PSVersionTable` 69 | placeholder: | 70 | Add PowerShell information here 71 | render: text 72 | validations: 73 | required: true 74 | - type: textarea 75 | id: moduleVersion 76 | attributes: 77 | label: PSMSALNet version 78 | description: | 79 | Please provide the version of the PSMSALNet module that was used. _Will be automatically formatted as plain text._ 80 | 81 | To help with this information, please run this command: `Get-Module -Name 'PSMSALNet' -ListAvailable | ft Name,Version,Path` 82 | placeholder: | 83 | Add module information here 84 | render: text 85 | validations: 86 | required: true 87 | 88 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "_runner": "terminal", 4 | "windows": { 5 | "options": { 6 | "shell": { 7 | "executable": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", 8 | "args": [ 9 | "-NoProfile", 10 | "-ExecutionPolicy", 11 | "Bypass", 12 | "-Command" 13 | ] 14 | } 15 | } 16 | }, 17 | "linux": { 18 | "options": { 19 | "shell": { 20 | "executable": "/usr/bin/pwsh", 21 | "args": [ 22 | "-NoProfile", 23 | "-Command" 24 | ] 25 | } 26 | } 27 | }, 28 | "osx": { 29 | "options": { 30 | "shell": { 31 | "executable": "/usr/local/bin/pwsh", 32 | "args": [ 33 | "-NoProfile", 34 | "-Command" 35 | ] 36 | } 37 | } 38 | }, 39 | "tasks": [ 40 | { 41 | "label": "build", 42 | "type": "shell", 43 | "command": "&${cwd}/build.ps1", 44 | "args": [], 45 | "presentation": { 46 | "echo": true, 47 | "reveal": "always", 48 | "focus": true, 49 | "panel": "new", 50 | "clear": false 51 | }, 52 | "runOptions": { 53 | "runOn": "default" 54 | }, 55 | "problemMatcher": [ 56 | { 57 | "owner": "powershell", 58 | "fileLocation": [ 59 | "absolute" 60 | ], 61 | "severity": "error", 62 | "pattern": [ 63 | { 64 | "regexp": "^\\s*(\\[-\\]\\s*.*?)(\\d+)ms\\s*$", 65 | "message": 1 66 | }, 67 | { 68 | "regexp": "(.*)", 69 | "code": 1 70 | }, 71 | { 72 | "regexp": "" 73 | }, 74 | { 75 | "regexp": "^.*,\\s*(.*):\\s*line\\s*(\\d+).*", 76 | "file": 1, 77 | "line": 2 78 | } 79 | ] 80 | } 81 | ] 82 | }, 83 | { 84 | "label": "test", 85 | "type": "shell", 86 | "command": "&${cwd}/build.ps1", 87 | "args": ["-AutoRestore","-Tasks","test"], 88 | "presentation": { 89 | "echo": true, 90 | "reveal": "always", 91 | "focus": true, 92 | "panel": "dedicated", 93 | "showReuseMessage": true, 94 | "clear": false 95 | }, 96 | "problemMatcher": [ 97 | { 98 | "owner": "powershell", 99 | "fileLocation": [ 100 | "absolute" 101 | ], 102 | "severity": "error", 103 | "pattern": [ 104 | { 105 | "regexp": "^\\s*(\\[-\\]\\s*.*?)(\\d+)ms\\s*$", 106 | "message": 1 107 | }, 108 | { 109 | "regexp": "(.*)", 110 | "code": 1 111 | }, 112 | { 113 | "regexp": "" 114 | }, 115 | { 116 | "regexp": "^.*,\\s*(.*):\\s*line\\s*(\\d+).*", 117 | "file": 1, 118 | "line": 2 119 | } 120 | ] 121 | } 122 | ] 123 | } 124 | ] 125 | } 126 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Problem_with_module.yml: -------------------------------------------------------------------------------- 1 | name: Problem with the module 2 | description: If you have a problem using this module, want to report a bug, or suggest an enhancement to this module. 3 | labels: [] 4 | assignees: [] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | TITLE: Please be descriptive not sensationalist. 10 | 11 | Your feedback and support is greatly appreciated, thanks for contributing! 12 | 13 | Please provide information regarding your issue under each section below. 14 | **Write N/A in sections that do not apply, or if the information is not available.** 15 | - type: textarea 16 | id: description 17 | attributes: 18 | label: Problem description 19 | description: Details of the scenario you tried and the problem that is occurring, or the enhancement you are suggesting. 20 | validations: 21 | required: true 22 | - type: textarea 23 | id: logs 24 | attributes: 25 | label: Verbose logs 26 | description: | 27 | Verbose logs showing the problem. **NOTE! Sensitive information should be obfuscated.** _Will be automatically formatted as plain text._ 28 | placeholder: | 29 | Paste verbose logs here 30 | render: text 31 | validations: 32 | required: true 33 | - type: textarea 34 | id: reproducible 35 | attributes: 36 | label: How to reproduce 37 | description: Provide the steps to reproduce the problem. 38 | validations: 39 | required: true 40 | - type: textarea 41 | id: expectedBehavior 42 | attributes: 43 | label: Expected behavior 44 | description: Describe what you expected to happen. 45 | validations: 46 | required: true 47 | - type: textarea 48 | id: currentBehavior 49 | attributes: 50 | label: Current behavior 51 | description: Describe what actually happens. 52 | validations: 53 | required: true 54 | - type: textarea 55 | id: suggestedSolution 56 | attributes: 57 | label: Suggested solution 58 | description: Do you have any suggestions how to solve the issue? 59 | validations: 60 | required: true 61 | - type: textarea 62 | id: targetNodeOS 63 | attributes: 64 | label: Operating system the target node is running 65 | description: | 66 | Please provide as much as possible about the node running PSMSALNet. _Will be automatically formatted as plain text._ 67 | 68 | To help with this information: 69 | - On a Linux distribution, please provide the distribution name, version, and release. The following command can help get this information: `cat /etc/*-release && cat /proc/version` 70 | - On a Windows OS please provide edition, version, build, and language. The following command can help get this information: `Get-ComputerInfo -Property @('OsName','OsOperatingSystemSKU','OSArchitecture','WindowsVersion','WindowsBuildLabEx','OsLanguage','OsMuiLanguages')` 71 | placeholder: | 72 | Add operating system information here 73 | render: text 74 | validations: 75 | required: true 76 | - type: textarea 77 | id: targetNodePS 78 | attributes: 79 | label: PowerShell version and build the target node is running 80 | description: | 81 | Please provide the version and build of PowerShell the target node is running. _Will be automatically formatted as plain text._ 82 | 83 | To help with this information, please run this command: `$PSVersionTable` 84 | placeholder: | 85 | Add PowerShell information here 86 | render: text 87 | validations: 88 | required: true 89 | - type: textarea 90 | id: moduleVersion 91 | attributes: 92 | label: Module version used 93 | description: | 94 | Please provide the version of the PSMSALNet module that was used. _Will be automatically formatted as plain text._ 95 | 96 | To help with this information, please run this command: `Get-Module -Name 'PSMSALNet' -ListAvailable | ft Name,Version,Path` 97 | placeholder: | 98 | Add module information here 99 | render: text 100 | validations: 101 | required: true 102 | 103 | -------------------------------------------------------------------------------- /source/Public/ConvertFrom-Jwt.ps1: -------------------------------------------------------------------------------- 1 | function ConvertFrom-Jwt 2 | { 3 | <# 4 | .SYNOPSIS 5 | This function will decode a base64 JWT token. 6 | .DESCRIPTION 7 | Big thank you to both Darren Robinson (https://github.com/darrenjrobinson/JWTDetails/blob/master/JWTDetails/1.0.0/JWTDetails.psm1) and 8 | Mehrdad Mirreza in the comment of the blog post (https://www.michev.info/Blog/Post/2140/decode-jwt-access-and-id-tokens-via-powershell) 9 | I've used both article for inspiration because: 10 | Darren does not have header wich is a mandatory peace according to me and Mehrdad does not have signature which is also a mandatory piece. 11 | .PARAMETER Token 12 | Specify the access token you want to decode 13 | .EXAMPLE 14 | PS> ConvertFrom-Jwt -Token "ey...." 15 | 16 | "will decode the token" 17 | .NOTES 18 | VERSION HISTORY 19 | 1.0 | 2021/07/06 | Francois LEON 20 | initial version 21 | POSSIBLE IMPROVEMENT 22 | - 23 | #> 24 | [cmdletbinding()] 25 | param ( 26 | [Parameter(Mandatory = $true)] 27 | [string]$Token 28 | ) 29 | 30 | Write-Verbose "[$((Get-Date).TimeofDay)] Starting $($myinvocation.mycommand)" 31 | 32 | Write-Verbose "[$((Get-Date).TimeofDay)] $($myinvocation.mycommand) - Remove Bearer word just in case" 33 | $Token = $Token.Replace('Bearer ', '') 34 | 35 | try 36 | { 37 | # Validate as per https://tools.ietf.org/html/rfc7519 38 | # Access and ID tokens are fine, Refresh tokens will not work 39 | if (!$Token.Contains('.') -or !$Token.StartsWith('eyJ')) 40 | { 41 | Throw 'Invalid token' 42 | } 43 | 44 | # Extract header and payload 45 | $tokenheader, $tokenPayload, $tokensignature = $Token.Split('.').Replace('-', '+').Replace('_', '/')[0..2] 46 | 47 | # Fix padding as needed, keep adding '=' until string length modulus 4 reaches 0 48 | while ($tokenheader.Length % 4) 49 | { 50 | Write-Debug 'Invalid length for a Base-64 char array or string, adding ='; $tokenheader += '=' 51 | } 52 | while ($tokenPayload.Length % 4) 53 | { 54 | Write-Debug 'Invalid length for a Base-64 char array or string, adding ='; $tokenPayload += '=' 55 | } 56 | while ($tokenSignature.Length % 4) 57 | { 58 | Write-Debug 'Invalid length for a Base-64 char array or string, adding ='; $tokenSignature += '=' 59 | } 60 | 61 | Write-Verbose "[$((Get-Date).TimeofDay)] $($myinvocation.mycommand) - Base64 encoded (padded) header:`n$tokenheader" 62 | Write-Verbose "[$((Get-Date).TimeofDay)] $($myinvocation.mycommand) - Base64 encoded (padded) payoad:`n$tokenPayload" 63 | Write-Verbose "[$((Get-Date).TimeofDay)] $($myinvocation.mycommand) - Base64 encoded (padded) payoad:`n$tokenSignature" 64 | 65 | # Convert header from Base64 encoded string to PSObject all at once 66 | $header = [System.Text.Encoding]::ASCII.GetString([system.convert]::FromBase64String($tokenheader)) | ConvertFrom-Json 67 | 68 | # Convert payload to string array 69 | $tokenArray = [System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String($tokenPayload)) 70 | 71 | # Convert from JSON to PSObject 72 | $tokobj = $tokenArray | ConvertFrom-Json 73 | 74 | # Convert Expiry time to PowerShell DateTime 75 | $orig = (Get-Date -Year 1970 -Month 1 -Day 1 -hour 0 -Minute 0 -Second 0 -Millisecond 0) 76 | $timeZone = Get-TimeZone 77 | $utcTime = $orig.AddSeconds($tokobj.exp) 78 | $hoursOffset = $timeZone.GetUtcOffset($(Get-Date)).hours #Daylight saving needs to be calculated 79 | $localTime = $utcTime.AddHours($hoursOffset) # Return local time, 80 | 81 | # Time to Expiry 82 | $timeToExpiry = ($localTime - (get-date)) 83 | 84 | Write-Verbose "[$((Get-Date).TimeofDay)] Ending $($myinvocation.mycommand)" 85 | [pscustomobject]@{ 86 | Tokenheader = $header 87 | TokenPayload = $tokobj 88 | TokenSignature = $tokenSignature 89 | TokenExpiryDateTime = $localTime 90 | TokentimeToExpiry = $timeToExpiry 91 | } 92 | } 93 | catch 94 | { 95 | $_.Exception.Message 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /source/PSMSALNet.psd1: -------------------------------------------------------------------------------- 1 | # 2 | # Module manifest for module 'PSMSALNet' 3 | # 4 | # Generated by: Scomnewbie 5 | # 6 | # Generated on: 9/21/2023 7 | # 8 | 9 | @{ 10 | 11 | # Script module or binary module file associated with this manifest. 12 | RootModule = 'PSMSALNet.psm1' 13 | 14 | # Version number of this module. 15 | ModuleVersion = '0.1.1' 16 | 17 | # Supported PSEditions 18 | # CompatiblePSEditions = @() 19 | 20 | # ID used to uniquely identify this module 21 | GUID = 'c52512aa-f12b-4956-8312-9cfeba907761' 22 | 23 | # Author of this module 24 | Author = 'Francois LEON' 25 | 26 | # Company or vendor of this module 27 | CompanyName = 'Scomnewbie' 28 | 29 | # Copyright statement for this module 30 | Copyright = '(c) Scomnewbie. All rights reserved.' 31 | 32 | # Description of the functionality provided by this module 33 | Description = 'Module used to interact with Microsoft Authentication library' 34 | 35 | # Minimum version of the PowerShell engine required by this module 36 | PowerShellVersion = '7.4' 37 | 38 | # Name of the PowerShell host required by this module 39 | # PowerShellHostName = '' 40 | 41 | # Minimum version of the PowerShell host required by this module 42 | # PowerShellHostVersion = '' 43 | 44 | # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 45 | # DotNetFrameworkVersion = '' 46 | 47 | # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 48 | # ClrVersion = '' 49 | 50 | # Processor architecture (None, X86, Amd64) required by this module 51 | # ProcessorArchitecture = '' 52 | 53 | # Modules that must be imported into the global environment prior to importing this module 54 | # RequiredModules = @() 55 | 56 | # Assemblies that must be loaded prior to importing this module 57 | RequiredAssemblies = @('./lib/Microsoft.Identity.Client.dll', './lib/Microsoft.IdentityModel.Abstractions.dll', './lib/Microsoft.Identity.Client.Broker.dll', './lib/Microsoft.Identity.Client.Extensions.Msal.dll', './lib/DeviceCodeHelper.dll', './lib/Microsoft.Identity.Client.NativeInterop.dll','./lib/PSMSALNetHelper.dll') 58 | 59 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 60 | # ScriptsToProcess = @() 61 | 62 | # Type files (.ps1xml) to be loaded when importing this module 63 | # TypesToProcess = @() 64 | 65 | # Format files (.ps1xml) to be loaded when importing this module 66 | # FormatsToProcess = @() 67 | 68 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 69 | # NestedModules = @() 70 | 71 | # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. 72 | FunctionsToExport = @() 73 | 74 | # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. 75 | CmdletsToExport = @() 76 | 77 | # Variables to export from this module 78 | VariablesToExport = @() 79 | 80 | # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. 81 | AliasesToExport = @() 82 | 83 | # DSC resources to export from this module 84 | #DscResourcesToExport = @() 85 | 86 | # List of all modules packaged with this module 87 | # ModuleList = @() 88 | 89 | # List of all files packaged with this module 90 | # FileList = @() 91 | 92 | # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. 93 | PrivateData = @{ 94 | 95 | PSData = @{ 96 | 97 | # Tags applied to this module. These help with module discovery in online galleries. 98 | # Tags = @() 99 | 100 | # A URL to the license for this module. 101 | # LicenseUri = '' 102 | 103 | # A URL to the main website for this project. 104 | ProjectUri = 'https://github.com/SCOMnewbie/PSMSALNet' 105 | 106 | # A URL to an icon representing this module. 107 | # IconUri = '' 108 | 109 | # ReleaseNotes of this module 110 | ReleaseNotes = '' 111 | 112 | # Prerelease string of this module 113 | Prerelease = '' 114 | 115 | # Flag to indicate whether the module requires explicit user acceptance for install/update/save 116 | # RequireLicenseAcceptance = $false 117 | 118 | # External dependent modules of this module 119 | # ExternalModuleDependencies = @() 120 | 121 | } # End of PSData hashtable 122 | 123 | } # End of PrivateData hashtable 124 | 125 | # HelpInfo URI of this module 126 | # HelpInfoURI = '' 127 | 128 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 129 | # DefaultCommandPrefix = '' 130 | 131 | } 132 | -------------------------------------------------------------------------------- /tests/helpers/scomnewbie.pem: -------------------------------------------------------------------------------- 1 | Bag Attributes 2 | localKeyID: 01 00 00 00 3 | friendlyName: te-4dc817bf-1f14-4b5d-a8e4-2d836796d07a 4 | Microsoft CSP Name: Microsoft Software Key Storage Provider 5 | Key Attributes 6 | X509v3 Key Usage: 90 7 | -----BEGIN PRIVATE KEY----- 8 | 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 57 | tjHjm3wQG1KILUF70oBj4hmlIuiNRQ== 58 | -----END PRIVATE KEY----- 59 | Bag Attributes 60 | localKeyID: 01 00 00 00 61 | 1.3.6.1.4.1.311.17.3.71: 46 00 4C 00 4F 00 2D 00 4C 00 41 00 50 00 2D 00 30 00 39 00 36 00 34 00 38 00 33 00 2E 00 75 00 62 00 69 00 73 00 6F 00 66 00 74 00 2E 00 6F 00 72 00 67 00 00 00 62 | subject=CN = cert02@comnewbie.com 63 | issuer=CN = cert02@comnewbie.com 64 | -----BEGIN CERTIFICATE----- 65 | MIIFDzCCAvegAwIBAgIQLhcfsAA6TpNB1tZFT1B/6DANBgkqhkiG9w0BAQsFADAf 66 | MR0wGwYDVQQDDBRjZXJ0MDJAY29tbmV3YmllLmNvbTAeFw0yMjA5MzAxODM5MzFa 67 | Fw0yMzA5MzAxODU5MzFaMB8xHTAbBgNVBAMMFGNlcnQwMkBjb21uZXdiaWUuY29t 68 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp/omW5nChxaBXWAcyhU9 69 | QIPKr+gLAjv7LnNqnRo25nMq/z01PV50ZoLgKhIahjJhtJwUokls5K/GIl14BWfW 70 | Izj6HDpHycgks6TYQ9yl7qCpUZ33q8gcKxusNmAIjA6/Ap18rsxrYhi3MbNTr48p 71 | uNog4ebxnUq2TFXrzGdHCQx72YbU5AQ4jtfvw4OjV6Et8FCBk43eOD64Y/kqWY1z 72 | ttxyxGgbRNECSPEYyUJLjSX+SF8nPRrUe77VeDdrC+BPB+lcUSzdkT+g2pT7ksQd 73 | TOsnhb3wmJXVKJx0EWfkXM+8rGjmpWC+OS+TPvIz8JfopR20YAyuSbjEy3YUK8Af 74 | YDrVgFWB0OfAAx8+F5cVoziqZbXCoc363lT4jdXiCpkj+tbABsN+RCkrUCTrurqN 75 | VqZFRycNz2VQ8fdnrqI+09ahijwSxXRavntzKbE/nj68dMuSAt4iyCyiYMsVGMn3 76 | zXlt0wcQtyChJ3hqBG1OPN86bBIJHBOuD+RtCAqitvZ2JZZKx5gvH/HA5+vXNqFk 77 | Z3s4Ao3Dh5X5yJ8s4kRk9aUcD9Ljkot5TCPUvfd2MjmBZ7RZL+r0lDlBnfrpjz4M 78 | 4dVTY8SdxS5vUysdRwkNtHhdmRtbr8GMYJXgmWSzaeNW7ljPJuUThAtRxpC5fqvq 79 | XRDfuIU2YiMs3t+u4C0+QkUCAwEAAaNHMEUwDgYDVR0PAQH/BAQDAgQQMBQGA1Ud 80 | JQQNMAsGCSsGAQQBgjdQATAdBgNVHQ4EFgQU7pu5z0afU43/pNp+Za+CX8dEQ7Qw 81 | DQYJKoZIhvcNAQELBQADggIBABOlODhgYs50lnrX/lw6IW80ElVudVRZEmLOzNlO 82 | b8iP89qnawVyC5TVu9kvfOWYe9G2FwSSFTUJCggecSDisqTxz1VlKt4UqaOAJ8Fl 83 | D0odjEeB92TZK/NDwvBEUqkThlM/B0o9FhK/r/v437SBGJMkzJnEIgMYogYel/mV 84 | bT1/uDqvCu7yRIb/xrPk0wyblc1vBwDkvZ1wnfWY7ME8NmQ8qQ71y7o/GZbTh9VG 85 | ZIolbrBIyUOH1u+Xu2kSOZtIBX6ujQ688jUumdUSN4oGz0yJjb2j/t6j6CGhob2v 86 | qZ3EJlE9McBI0X744M/bk0/bAwCogE7wRDvswr62x8zcrjqogHa+EQrZ3joG/Ijq 87 | QL2b8Vlt7LEhMVD3lB7xZq17NVdq0RQMscUYB5f0968mv6lU+3t5ML1iM18S5OY9 88 | /yca/xJl1c/9jimzQNVCihsfyeZpkoXj2g4oiVdBQ9RE4l0H6mymeaTReSjJ/q3z 89 | caO5smf1eCOrAkLGb+c+YWTbp2JwWWLCLKs1IkH9xCNAVhlNBI/IGFAwYNCMIE9f 90 | iyw3xPnOu9mw431UZpiQmmYx7IDID5C1TOGrUI5bSTlF6JZx8YPS/oKbZJ0cey5W 91 | JLu4SfUQ/K1mmyx9ngLwKyA73zu3t8vPLSB2Fk2Bl81n9ejYmYwhq21awoDukukr 92 | /FEs 93 | -----END CERTIFICATE----- 94 | -------------------------------------------------------------------------------- /tests/helpers/scomnewbie2.pem: -------------------------------------------------------------------------------- 1 | Bag Attributes 2 | localKeyID: 01 00 00 00 3 | friendlyName: te-4dc817bf-1f14-4b5d-a8e4-2d836796d07a 4 | Microsoft CSP Name: Microsoft Software Key Storage Provider 5 | Key Attributes 6 | X509v3 Key Usage: 90 7 | -----BEGIN ENCRYPTED PRIVATE KEY----- 8 | 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 60 | -----END ENCRYPTED PRIVATE KEY----- 61 | Bag Attributes 62 | localKeyID: 01 00 00 00 63 | 1.3.6.1.4.1.311.17.3.71: 46 00 4C 00 4F 00 2D 00 4C 00 41 00 50 00 2D 00 30 00 39 00 36 00 34 00 38 00 33 00 2E 00 75 00 62 00 69 00 73 00 6F 00 66 00 74 00 2E 00 6F 00 72 00 67 00 00 00 64 | subject=CN = cert02@comnewbie.com 65 | issuer=CN = cert02@comnewbie.com 66 | -----BEGIN CERTIFICATE----- 67 | MIIFDzCCAvegAwIBAgIQLhcfsAA6TpNB1tZFT1B/6DANBgkqhkiG9w0BAQsFADAf 68 | MR0wGwYDVQQDDBRjZXJ0MDJAY29tbmV3YmllLmNvbTAeFw0yMjA5MzAxODM5MzFa 69 | Fw0yMzA5MzAxODU5MzFaMB8xHTAbBgNVBAMMFGNlcnQwMkBjb21uZXdiaWUuY29t 70 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp/omW5nChxaBXWAcyhU9 71 | QIPKr+gLAjv7LnNqnRo25nMq/z01PV50ZoLgKhIahjJhtJwUokls5K/GIl14BWfW 72 | Izj6HDpHycgks6TYQ9yl7qCpUZ33q8gcKxusNmAIjA6/Ap18rsxrYhi3MbNTr48p 73 | uNog4ebxnUq2TFXrzGdHCQx72YbU5AQ4jtfvw4OjV6Et8FCBk43eOD64Y/kqWY1z 74 | ttxyxGgbRNECSPEYyUJLjSX+SF8nPRrUe77VeDdrC+BPB+lcUSzdkT+g2pT7ksQd 75 | TOsnhb3wmJXVKJx0EWfkXM+8rGjmpWC+OS+TPvIz8JfopR20YAyuSbjEy3YUK8Af 76 | YDrVgFWB0OfAAx8+F5cVoziqZbXCoc363lT4jdXiCpkj+tbABsN+RCkrUCTrurqN 77 | VqZFRycNz2VQ8fdnrqI+09ahijwSxXRavntzKbE/nj68dMuSAt4iyCyiYMsVGMn3 78 | zXlt0wcQtyChJ3hqBG1OPN86bBIJHBOuD+RtCAqitvZ2JZZKx5gvH/HA5+vXNqFk 79 | Z3s4Ao3Dh5X5yJ8s4kRk9aUcD9Ljkot5TCPUvfd2MjmBZ7RZL+r0lDlBnfrpjz4M 80 | 4dVTY8SdxS5vUysdRwkNtHhdmRtbr8GMYJXgmWSzaeNW7ljPJuUThAtRxpC5fqvq 81 | XRDfuIU2YiMs3t+u4C0+QkUCAwEAAaNHMEUwDgYDVR0PAQH/BAQDAgQQMBQGA1Ud 82 | JQQNMAsGCSsGAQQBgjdQATAdBgNVHQ4EFgQU7pu5z0afU43/pNp+Za+CX8dEQ7Qw 83 | DQYJKoZIhvcNAQELBQADggIBABOlODhgYs50lnrX/lw6IW80ElVudVRZEmLOzNlO 84 | b8iP89qnawVyC5TVu9kvfOWYe9G2FwSSFTUJCggecSDisqTxz1VlKt4UqaOAJ8Fl 85 | D0odjEeB92TZK/NDwvBEUqkThlM/B0o9FhK/r/v437SBGJMkzJnEIgMYogYel/mV 86 | bT1/uDqvCu7yRIb/xrPk0wyblc1vBwDkvZ1wnfWY7ME8NmQ8qQ71y7o/GZbTh9VG 87 | ZIolbrBIyUOH1u+Xu2kSOZtIBX6ujQ688jUumdUSN4oGz0yJjb2j/t6j6CGhob2v 88 | qZ3EJlE9McBI0X744M/bk0/bAwCogE7wRDvswr62x8zcrjqogHa+EQrZ3joG/Ijq 89 | QL2b8Vlt7LEhMVD3lB7xZq17NVdq0RQMscUYB5f0968mv6lU+3t5ML1iM18S5OY9 90 | /yca/xJl1c/9jimzQNVCihsfyeZpkoXj2g4oiVdBQ9RE4l0H6mymeaTReSjJ/q3z 91 | caO5smf1eCOrAkLGb+c+YWTbp2JwWWLCLKs1IkH9xCNAVhlNBI/IGFAwYNCMIE9f 92 | iyw3xPnOu9mw431UZpiQmmYx7IDID5C1TOGrUI5bSTlF6JZx8YPS/oKbZJ0cey5W 93 | JLu4SfUQ/K1mmyx9ngLwKyA73zu3t8vPLSB2Fk2Bl81n9ejYmYwhq21awoDukukr 94 | /FEs 95 | -----END CERTIFICATE----- 96 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog for PSMSALNet 2 | 3 | The format is based on and uses the types of changes according to [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 4 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 5 | 6 | ## [Unreleased] 7 | 8 | ### Added 9 | 10 | - N/A 11 | 12 | ## [0.1.3] - 2025-10-29 13 | 14 | ### Added 15 | 16 | - Added Kusto audience as non custom audience 17 | 18 | ## [0.1.2] - 2025-08-28 19 | 20 | ### Added 21 | 22 | - Added EventHUB audience as non custom audience 23 | 24 | ## [0.1.1] - 2024-11-02 25 | 26 | ### Added 27 | 28 | - Bump MSAL version to 4.66.1 + all external dependencies 29 | 30 | ### Tested 31 | 32 | #### Public Authorization Code Flow (PKCE) 33 | 34 | [x] Get-EntraToken -PublicAuthorizationCodeFlow -ClientId $clientID -TenantId $TenantId -Resource GraphAPI -Permissions @("user.read") -Verbose 35 | [x] Get-EntraToken -PublicAuthorizationCodeFlow -ClientId $clientID -TenantId $TenantId -Resource GraphAPI -Permissions @("user.read") -WithDebugLogging -Verbose 36 | [x] Get-EntraToken -PublicAuthorizationCodeFlow -ClientId $clientID -TenantId $TenantId -Resource GraphAPI -Permissions @("user.read") -WithDebugLogging -WithLocalCaching -Verbose 37 | [x] Get-EntraToken -PublicAuthorizationCodeFlow -ClientId $clientID -TenantId $TenantId -Resource GraphAPI -Permissions @("user.read") -WithDebugLogging -WithLocalCaching -TokenSerializationPath C:\TEMP -verbose 38 | [x] Get-EntraToken -PublicAuthorizationCodeFlow -ClientId $clientID -TenantId $TenantId -Resource GraphAPI -Permissions @("user.read") -ExtraScopesToConsent @("api://PSMSALNet-backend/Access.AsUser") 39 | [x] Get-EntraToken -PublicAuthorizationCodeFlow -ClientId $clientID -TenantId $TenantId -Resource Custom -CustomResource "api://PSMSALNet-backend" -Permissions @("Access.AsUser") 40 | 41 | #### Public Device Code Flow 42 | [x] Get-EntraToken -DeviceCodeFlow -ClientId $ClientId -TenantId $TenantId -Resource GraphAPI -Permissions @("user.read") 43 | [ ] Get-EntraToken -DeviceCodeFlow -ClientId $ClientId -TenantId $TenantId -Resource GraphAPI -Permissions @("user.read") -WithDebugLogging ->No logs provided 44 | 45 | #### WAM Flow 46 | [x] Get-EntraToken -WAMFlow -ClientId $ClientId -TenantId $TenantId -RedirectUri "ms-appx-web://Microsoft.AAD.BrokerPlugin/$clientId" -Resource GraphAPI -Permissions @("user.read") 47 | 48 | #### OBO with secret 49 | [x] Get-EntraToken -OnBehalfFlowWithSecret -ClientId '' -ClientSecret '' -UserAssertion -TenantId $tenantId -Resource GraphAPI -Permissions @("User.Read.All") 50 | 51 | #### Client credential flow secret 52 | 53 | [x] Get-EntraToken -ClientCredentialFlowWithSecret -ClientId $ClientId -ClientSecret $ClientSecret -TenantId $tenantId -Resource GraphAPI -WithDebugLogging 54 | 55 | #### Client credential with certificate 56 | $X509 = ConvertTo-X509Certificate2 -PfxPath C:\TEMP\testcertauth.pfx -Password $(ConvertTo-SecureString -String 'CertPassword' -AsPlainText -Force) -Verbose 57 | [x] Get-EntraToken -ClientCredentialFlowWithCertificate -ClientId $ClientId -TenantId $TenantId -ClientCertificate $X509 -Resource GraphAPI -WithDebugLogging -Verbose 58 | 59 | #### OBO with certificate 60 | $X509 = ConvertTo-X509Certificate2 -PfxPath C:\TEMP\testcertauth.pfx -Password $(ConvertTo-SecureString -String 'CertPassword' -AsPlainText -Force) -Verbose 61 | $t = Get-EntraToken -PublicAuthorizationCodeFlow -ClientId -TenantId $TenantId -Resource Custom -CustomResource api://PSMSALNet-backend -Permissions @("Access.AsUser") | % AccessToken 62 | [x] Get-EntraToken -OnBehalfFlowWithCertificate -ClientId '' -ClientCertificate $X509 -UserAssertion $t -TenantId $TenantId -Resource GraphAPI -Permissions @("User.Read.All") 63 | 64 | #### Federated Flow (Not tested yet) 65 | 66 | ## [0.1.0] - 2024-05-15 67 | 68 | ### Added 69 | 70 | - Added ConvertFrom-Jwt function 71 | - Added ConvertFrom-Jwt tests 72 | 73 | ## [0.0.9] - 2024-05-10 74 | 75 | ### Added 76 | 77 | - Bump in MSAL version (4.60.3) + all external dependencies 78 | - Add Get-EntraToken more managed identity exemples into the functions 79 | - Bump to net8.0 (Powershell 7.4) 80 | 81 | ### Fixed 82 | 83 | - Add FR language support in ConvertTo-X509Certificate2.Tests.ps1 to validate error message. 84 | 85 | ## [0.0.8] - 2023-10-24 86 | 87 | ### Added 88 | 89 | - MSAL cache on filesystem available for public application (Auth code with PKCE, device code) to be resilient to console restart. 90 | - Examples to use local MSAL token serialization. 91 | 92 | ### Fixed 93 | 94 | - Clean useless code regarding client credential flow. 95 | 96 | ## [0.0.7] - 2023-10-16 97 | 98 | ### Fixed 99 | 100 | - Following Azure [ARC for Linux issue confirmed by the MSAL.Net team](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/4358), a temporary fix is provided until the day MSAL.Net will include this feature. This flow, only generate an access token and doesn't use the MSAL memory cache. 101 | 102 | ## [0.0.6] - 2023-10-13 103 | 104 | ### Fixed 105 | 106 | - FederatedCredentialFlowWithAssertion was broken with message 'AADSTS50027: JWT token is invalid or malformed', MSAL is waiting for a string directly instead of a userassignment object. 107 | 108 | ### Added 109 | 110 | - Documentation about FederatedCredentialFlowWithAssertion parameter and a real Kubernetes example under Examples\aks-workloadidentity 111 | - README is updated for FederatedCredentialFlowWithAssertion parameter 112 | - Add new FederatedCredentialFlowWithAssertion flow example in the Get-Entra cmdlet 113 | 114 | ## [0.0.5] - 2023-10-06 115 | 116 | ### Added 117 | 118 | - Add MSAL logging with the WithDebugLogging parameter 119 | - Add PSMSALNetHelper.dll library 120 | 121 | ## [0.0.4] - 2023-09-27 122 | 123 | ### Added 124 | 125 | - Add new WAM flow example in the Get-Entra cmdlet 126 | - Add new WAM flow example in the README 127 | - Add new OBO flow examples (secret + certificate) in README 128 | - Add new OBO flow example in the Get-Entra cmdlet 129 | - Add a lot of system managed identity examples 130 | - Project URL in powershell gallery 131 | 132 | ## [0.0.3] - 2023-09-25 133 | 134 | ### Fixed 135 | 136 | - The private cmdlet exposed by the WAMHelper.dll which is required for the -WAMflow parameter wasn't loaded into the module through the RequiredAssemblies into the psd1 file. The module is now manually added to the psm1 file with the prefix.ps1 script and the build.yml parameter file. 137 | 138 | ## [0.0.2] - 2023-09-25 139 | 140 | ### Added 141 | 142 | - Add new client credential flow example in the Get-Entra cmdlet 143 | - Add new client credential flow example in the README 144 | - Add new authorization code flow example in the Get-Entra cmdlet 145 | - Add new authorization code flow example in the README 146 | - Add new device code flow example in the Get-Entra cmdlet 147 | - Add new device code flow example in the README 148 | 149 | ### Changed 150 | 151 | - Get-EntraToken -ClientCredentialFlowWithSecret output a non necessary line in the output. Remove it. 152 | - Get-EntraToken -PublicAuthorizationCodeFlow output a non necessary line in the output. Remove it. 153 | - Added new WAMHelper version without dotnet framwork requirement. 154 | 155 | ### Removed 156 | 157 | - N/A 158 | 159 | ## [0.0.1] - 2023-09-23 160 | 161 | ### Added 162 | 163 | - This is the initial version 164 | 165 | ### Changed 166 | 167 | - N/A 168 | 169 | ### Removed 170 | 171 | - N/A 172 | -------------------------------------------------------------------------------- /tests/QA/module.tests.ps1: -------------------------------------------------------------------------------- 1 | BeforeDiscovery { 2 | $projectPath = "$($PSScriptRoot)\..\.." | Convert-Path 3 | 4 | <# 5 | If the QA tests are run outside of the build script (e.g with Invoke-Pester) 6 | the parent scope has not set the variable $ProjectName. 7 | #> 8 | if (-not $ProjectName) 9 | { 10 | # Assuming project folder name is project name. 11 | $ProjectName = Get-SamplerProjectName -BuildRoot $projectPath 12 | } 13 | 14 | $script:moduleName = $ProjectName 15 | 16 | Remove-Module -Name $script:moduleName -Force -ErrorAction SilentlyContinue 17 | 18 | $mut = Get-Module -Name $script:moduleName -ListAvailable | 19 | Select-Object -First 1 | 20 | Import-Module -Force -ErrorAction Stop -PassThru 21 | } 22 | 23 | BeforeAll { 24 | # Convert-Path required for PS7 or Join-Path fails 25 | $projectPath = "$($PSScriptRoot)\..\.." | Convert-Path 26 | 27 | <# 28 | If the QA tests are run outside of the build script (e.g with Invoke-Pester) 29 | the parent scope has not set the variable $ProjectName. 30 | #> 31 | if (-not $ProjectName) 32 | { 33 | # Assuming project folder name is project name. 34 | $ProjectName = Get-SamplerProjectName -BuildRoot $projectPath 35 | } 36 | 37 | $script:moduleName = $ProjectName 38 | 39 | $sourcePath = ( 40 | Get-ChildItem -Path $projectPath\*\*.psd1 | 41 | Where-Object -FilterScript { 42 | ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) ` 43 | -and $( 44 | try 45 | { 46 | Test-ModuleManifest -Path $_.FullName -ErrorAction Stop 47 | } 48 | catch 49 | { 50 | $false 51 | } 52 | ) 53 | } 54 | ).Directory.FullName 55 | } 56 | 57 | Describe 'Changelog Management' -Tag 'Changelog' { 58 | It 'Changelog has been updated' -Skip:( 59 | -not ([bool](Get-Command git -ErrorAction SilentlyContinue) -and 60 | [bool](&(Get-Process -Id $PID).Path -NoProfile -Command 'git rev-parse --is-inside-work-tree 2>$null')) 61 | ) { 62 | # Get the list of changed files compared with branch main 63 | $headCommit = &git rev-parse HEAD 64 | $defaultBranchCommit = &git rev-parse origin/main 65 | $filesChanged = &git @('diff', "$defaultBranchCommit...$headCommit", '--name-only') 66 | $filesStagedAndUnstaged = &git @('diff', 'HEAD', '--name-only') 67 | 68 | $filesChanged += $filesStagedAndUnstaged 69 | 70 | # Only check if there are any changed files. 71 | if ($filesChanged) 72 | { 73 | $filesChanged | Should -Contain 'CHANGELOG.md' -Because 'the CHANGELOG.md must be updated with at least one entry in the Unreleased section for each PR' 74 | } 75 | } 76 | 77 | It 'Changelog format compliant with keepachangelog format' -Skip:(![bool](Get-Command git -EA SilentlyContinue)) { 78 | { Get-ChangelogData -Path (Join-Path $ProjectPath 'CHANGELOG.md') -ErrorAction Stop } | Should -Not -Throw 79 | } 80 | 81 | It 'Changelog should have an Unreleased header' -Skip:$skipTest { 82 | (Get-ChangelogData -Path (Join-Path -Path $ProjectPath -ChildPath 'CHANGELOG.md') -ErrorAction Stop).Unreleased | Should -Not -BeNullOrEmpty 83 | } 84 | } 85 | 86 | Describe 'General module control' -Tags 'FunctionalQuality' { 87 | It 'Should import without errors' { 88 | { Import-Module -Name $script:moduleName -Force -ErrorAction Stop } | Should -Not -Throw 89 | 90 | Get-Module -Name $script:moduleName | Should -Not -BeNullOrEmpty 91 | } 92 | 93 | It 'Should remove without error' { 94 | { Remove-Module -Name $script:moduleName -ErrorAction Stop } | Should -Not -Throw 95 | 96 | Get-Module $script:moduleName | Should -BeNullOrEmpty 97 | } 98 | } 99 | 100 | BeforeDiscovery { 101 | # Must use the imported module to build test cases. 102 | $allModuleFunctions = & $mut { Get-Command -Module $args[0] -CommandType Function } $script:moduleName 103 | 104 | # Build test cases. 105 | $testCases = @() 106 | 107 | foreach ($function in $allModuleFunctions) 108 | { 109 | $testCases += @{ 110 | Name = $function.Name 111 | } 112 | } 113 | } 114 | 115 | Describe 'Quality for module' -Tags 'TestQuality' { 116 | BeforeDiscovery { 117 | if (Get-Command -Name Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue) 118 | { 119 | $scriptAnalyzerRules = Get-ScriptAnalyzerRule 120 | } 121 | else 122 | { 123 | if ($ErrorActionPreference -ne 'Stop') 124 | { 125 | Write-Warning -Message 'ScriptAnalyzer not found!' 126 | } 127 | else 128 | { 129 | throw 'ScriptAnalyzer not found!' 130 | } 131 | } 132 | } 133 | 134 | It 'Should have a unit test for ' -ForEach $testCases { 135 | Get-ChildItem -Path 'tests\' -Recurse -Include "$Name.Tests.ps1" | Should -Not -BeNullOrEmpty 136 | } 137 | 138 | It 'Should pass Script Analyzer for ' -ForEach $testCases -Skip:(-not $scriptAnalyzerRules) { 139 | $functionFile = Get-ChildItem -Path $sourcePath -Recurse -Include "$Name.ps1" 140 | 141 | $pssaResult = (Invoke-ScriptAnalyzer -Path $functionFile.FullName) 142 | $report = $pssaResult | Format-Table -AutoSize | Out-String -Width 110 143 | $pssaResult | Should -BeNullOrEmpty -Because ` 144 | "some rule triggered.`r`n`r`n $report" 145 | } 146 | } 147 | 148 | Describe 'Help for module' -Tags 'helpQuality' { 149 | It 'Should have .SYNOPSIS for ' -ForEach $testCases { 150 | $functionFile = Get-ChildItem -Path $sourcePath -Recurse -Include "$Name.ps1" 151 | 152 | $scriptFileRawContent = Get-Content -Raw -Path $functionFile.FullName 153 | 154 | $abstractSyntaxTree = [System.Management.Automation.Language.Parser]::ParseInput($scriptFileRawContent, [ref] $null, [ref] $null) 155 | 156 | $astSearchDelegate = { $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] } 157 | 158 | $parsedFunction = $abstractSyntaxTree.FindAll( $astSearchDelegate, $true ) | 159 | Where-Object -FilterScript { 160 | $_.Name -eq $Name 161 | } 162 | 163 | $functionHelp = $parsedFunction.GetHelpContent() 164 | 165 | $functionHelp.Synopsis | Should -Not -BeNullOrEmpty 166 | } 167 | 168 | It 'Should have a .DESCRIPTION with length greater than 40 characters for ' -ForEach $testCases { 169 | $functionFile = Get-ChildItem -Path $sourcePath -Recurse -Include "$Name.ps1" 170 | 171 | $scriptFileRawContent = Get-Content -Raw -Path $functionFile.FullName 172 | 173 | $abstractSyntaxTree = [System.Management.Automation.Language.Parser]::ParseInput($scriptFileRawContent, [ref] $null, [ref] $null) 174 | 175 | $astSearchDelegate = { $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] } 176 | 177 | $parsedFunction = $abstractSyntaxTree.FindAll($astSearchDelegate, $true) | 178 | Where-Object -FilterScript { 179 | $_.Name -eq $Name 180 | } 181 | 182 | $functionHelp = $parsedFunction.GetHelpContent() 183 | 184 | $functionHelp.Description.Length | Should -BeGreaterThan 40 185 | } 186 | 187 | It 'Should have at least one (1) example for ' -ForEach $testCases { 188 | $functionFile = Get-ChildItem -Path $sourcePath -Recurse -Include "$Name.ps1" 189 | 190 | $scriptFileRawContent = Get-Content -Raw -Path $functionFile.FullName 191 | 192 | $abstractSyntaxTree = [System.Management.Automation.Language.Parser]::ParseInput($scriptFileRawContent, [ref] $null, [ref] $null) 193 | 194 | $astSearchDelegate = { $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] } 195 | 196 | $parsedFunction = $abstractSyntaxTree.FindAll( $astSearchDelegate, $true ) | 197 | Where-Object -FilterScript { 198 | $_.Name -eq $Name 199 | } 200 | 201 | $functionHelp = $parsedFunction.GetHelpContent() 202 | 203 | $functionHelp.Examples.Count | Should -BeGreaterThan 0 204 | $functionHelp.Examples[0] | Should -Match ([regex]::Escape($function.Name)) 205 | $functionHelp.Examples[0].Length | Should -BeGreaterThan ($function.Name.Length + 10) 206 | 207 | } 208 | 209 | It 'Should have described all parameters for ' -ForEach $testCases { 210 | $functionFile = Get-ChildItem -Path $sourcePath -Recurse -Include "$Name.ps1" 211 | 212 | $scriptFileRawContent = Get-Content -Raw -Path $functionFile.FullName 213 | 214 | $abstractSyntaxTree = [System.Management.Automation.Language.Parser]::ParseInput($scriptFileRawContent, [ref] $null, [ref] $null) 215 | 216 | $astSearchDelegate = { $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] } 217 | 218 | $parsedFunction = $abstractSyntaxTree.FindAll( $astSearchDelegate, $true ) | 219 | Where-Object -FilterScript { 220 | $_.Name -eq $Name 221 | } 222 | 223 | $functionHelp = $parsedFunction.GetHelpContent() 224 | 225 | $parameters = $parsedFunction.Body.ParamBlock.Parameters.Name.VariablePath.ForEach({ $_.ToString() }) 226 | 227 | foreach ($parameter in $parameters) 228 | { 229 | $functionHelp.Parameters.($parameter.ToUpper()) | Should -Not -BeNullOrEmpty -Because ('the parameter {0} must have a description' -f $parameter) 230 | $functionHelp.Parameters.($parameter.ToUpper()).Length | Should -BeGreaterThan 25 -Because ('the parameter {0} must have descriptive description' -f $parameter) 231 | } 232 | } 233 | } 234 | 235 | -------------------------------------------------------------------------------- /source/Public/ConvertTo-X509Certificate2.ps1: -------------------------------------------------------------------------------- 1 | function ConvertTo-X509Certificate2 2 | { 3 | <# 4 | .SYNOPSIS 5 | This function will output a X509Certificate2 certificate. 6 | .DESCRIPTION 7 | Because Linux does not have the Cert: provider (Get-PsProvider), we have to find a way to use the provided certificate on all platforms and X509 8 | is a solution. This function will inject several format (cer,crt,pem,pfx) to expose the result in a standardized way. 9 | .PARAMETER PfxPath 10 | Specify path of a pfx file. 11 | .PARAMETER PemPath 12 | Specify path of a pem file. 13 | .PARAMETER CerPath 14 | Specify path of a cer file. 15 | .PARAMETER CrtPath 16 | Specify path of a crt file. 17 | .PARAMETER Password 18 | Specify password of a pfx file. 19 | .PARAMETER PrivateKeyPath 20 | Specify path of a decrypted private key. 21 | .PARAMETER KeyVaultCertificatePath 22 | Specify path of a specific certificate version hosted on Azure Key Vault. 23 | .PARAMETER AccessToken 24 | Specify an access token to contact the associated Key Vault. 25 | .PARAMETER APIVersion 26 | Specify the API version regarding Keyvault API for now the default value is 7.3. 27 | .PARAMETER ExportPrivateKey 28 | Specify you want to extract from Key Vault the certificate with the Private key. 29 | .EXAMPLE 30 | 31 | $PubCert = ConvertTo-X509Certificate2 -CerPath ./scomnewbie.cer 32 | 33 | Will generate a X509Certificate2 without private key from a cer file. 34 | 35 | .EXAMPLE 36 | 37 | $PubCert = ConvertTo-X509Certificate2 -CerPath ./scomnewbie.crt 38 | 39 | Will generate a X509Certificate2 without private key from a crt file. 40 | 41 | .EXAMPLE 42 | 43 | $PrivCert = ConvertTo-X509Certificate2 -PfxPath ./scomnewbie.pfx -Password $(ConvertTo-SecureString -String "exportpassword" -AsPlainText -Force) 44 | $PrivCert.PrivateKey 45 | 46 | Will generate a X509Certificate2 with private key from a pfx file. 47 | 48 | .EXAMPLE 49 | 50 | $PrivCert = ConvertTo-X509Certificate2 -PemPath ./scomnewbie2.pem -PrivateKeyPath ./privatekey_rsa.key 51 | $PrivCert.PrivateKey 52 | 53 | Will generate a X509Certificate2 with private key from a pem file. 54 | 55 | .EXAMPLE 56 | 57 | $CertURL = 'https://.vault.azure.net/certificates/test/5d69153b75214245ab72fa21b9c06bfb' 58 | $KVToken = (Get-AzAccessToken -Resource "https://vault.azure.net").Token #Once authenticated to Azure 59 | ConvertTo-X509Certificate2 -KeyVaultCertificatePath $CertURL -AccessToken $KVToken 60 | 61 | Will generate a X509Certificate2 with public key from a certificate hosted in Key Vault. 62 | .EXAMPLE 63 | 64 | $CertURL = 'https://.vault.azure.net/certificates/test/5d69153b75214245ab72fa21b9c06bfb' 65 | $KVToken = (Get-AzAccessToken -Resource "https://vault.azure.net").Token #Once authenticated to Azure 66 | ConvertTo-X509Certificate2 -KeyVaultCertificatePath $CertURL -AccessToken $KVToken -ExportPrivateKey 67 | 68 | Will generate a X509Certificate2 with private key from a certificate hosted in Key Vault. 69 | 70 | .NOTES 71 | VERSION HISTORY 72 | 1.0 | 2023/10/03 | Francois LEON 73 | initial version 74 | POSSIBLE IMPROVEMENT 75 | - 76 | #> 77 | [CmdletBinding()] 78 | [OutputType([System.Security.Cryptography.X509Certificates.X509Certificate2])] 79 | #[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword","")] 80 | param( 81 | [parameter(Mandatory, ParameterSetName = 'pfx')] 82 | [ValidateScript({ 83 | if ((Test-Path $_) -AND ($_ -like '*.pfx')) 84 | { 85 | $true 86 | } 87 | else 88 | { 89 | throw "Path $_ is not valid" 90 | } 91 | })] 92 | [String]$PfxPath, 93 | [parameter(Mandatory, ParameterSetName = 'pem')] 94 | [ValidateScript({ 95 | if ((Test-Path $_) -AND ($_ -like '*.pem')) 96 | { 97 | $true 98 | } 99 | else 100 | { 101 | throw "Path $_ is not valid" 102 | } 103 | })] 104 | [String]$PemPath, 105 | [parameter(Mandatory, ParameterSetName = 'crt')] 106 | [ValidateScript({ 107 | if ((Test-Path $_) -AND ($_ -like '*.crt')) 108 | { 109 | $true 110 | } 111 | else 112 | { 113 | throw "Path $_ is not valid" 114 | } 115 | })] 116 | [String]$CrtPath, 117 | [parameter(Mandatory, ParameterSetName = 'cer')] 118 | [ValidateScript({ 119 | if ((Test-Path $_) -AND ($_ -like '*.cer')) 120 | { 121 | $true 122 | } 123 | else 124 | { 125 | throw "Path $_ is not valid" 126 | } 127 | })] 128 | [String]$CerPath, 129 | [parameter(ParameterSetName = 'pfx')] 130 | [ValidateScript({ 131 | if ($_.Length -gt 0) 132 | { 133 | $true 134 | } 135 | else 136 | { 137 | throw 'SecureString argument contained no data.' 138 | } 139 | })] 140 | [securestring]$Password, 141 | [parameter(ParameterSetName = 'pem')] 142 | [ValidateScript({ 143 | if ((Test-Path $_) -AND ( $(Get-Content $_ | Select-Object -First 1) -eq '-----BEGIN PRIVATE KEY-----' )) 144 | { 145 | $true 146 | } 147 | else 148 | { 149 | throw "Path $_ is not valid or private key is not visible" 150 | } 151 | })] 152 | [string]$PrivateKeyPath, 153 | [parameter(Mandatory, ParameterSetName = 'keyvault')] 154 | [string]$KeyVaultCertificatePath, #https://ubuntukv415745.vault.azure.net/certificates/test/5d69153b75214245ab72fa21b9c06bfb 155 | [parameter(Mandatory, ParameterSetName = 'keyvault')] 156 | [string]$AccessToken, #(Get-AzAccessToken -Resource "https://vault.azure.net").Token 157 | [parameter(ParameterSetName = 'keyvault')] 158 | [string]$APIVersion = '7.3', 159 | [parameter(ParameterSetName = 'keyvault')] 160 | [switch]$ExportPrivateKey 161 | ) 162 | 163 | Begin 164 | { 165 | Write-Verbose "[$((Get-Date).TimeofDay)] Starting $($myinvocation.mycommand)" 166 | 167 | # Just keep what we need to avoid useless switch iteration 168 | $PSBoundParameters.Remove('KeyVaultAccessToken') | Out-Null 169 | $PSBoundParameters.Remove('ExportPrivateKey') | Out-Null 170 | $PSBoundParameters.Remove('PrivateKeyPath') | Out-Null 171 | $PSBoundParameters.Remove('Password') | Out-Null 172 | $PSBoundParameters.Remove('AccessToken') | Out-Null 173 | $PSBoundParameters.Remove('APIVersion') | Out-Null 174 | $PSBoundParameters.Remove('ExportPrivateKey') | Out-Null 175 | 176 | } #begin 177 | 178 | Process 179 | { 180 | switch ($PSBoundParameters.Keys) 181 | { 182 | 183 | 'CerPath' 184 | { 185 | #Even if it's not the same format crt and cer is using the same method. I will duplicate code for readability. 186 | [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($(Get-Item -Path $CerPath)) 187 | break 188 | } 189 | 190 | 'CrtPath' 191 | { 192 | [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($(Get-Item -Path $CrtPath)) 193 | break 194 | } 195 | 196 | 'PfxPath' 197 | { 198 | if ($Password) 199 | { 200 | #Means private key protected by password 201 | # Means Linux/Windows/MacOS running on Powershell 7 (Yes v6 does not count :D) 202 | [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($(Get-Item -Path $PfxPath), $(ConvertFrom-SecureString -SecureString $Password -AsPlainText)) 203 | break 204 | } 205 | else 206 | { 207 | #Means no password to protect the private key 208 | [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($(Get-Item -Path $PfxPath)) 209 | break 210 | } 211 | } 212 | 213 | 'PemPath' 214 | { 215 | if ($PrivateKeyPath) 216 | { 217 | #Means private key protected by password 218 | #openssl pkcs12 -in ./scomnewbie.pfx -out ./scomnewbie2.pem # Privatekey will be encrypted + no -nodes means passphrase required 219 | #openssl rsa -in ./scomnewbie2.pem -out privatekey_rsa.key #Enter passphrase + decode PK 220 | [System.Security.Cryptography.X509Certificates.X509Certificate2]::CreateFromPemFile($(Get-Item -Path $PemPath), $(Get-Item -Path $PrivateKeyPath)) 221 | break 222 | } 223 | else 224 | { 225 | #Means no password to protect the private key 226 | #openssl pkcs12 -in ./scomnewbie.pfx -out ./scomnewbie.pem -nodes # WARNING No more password anymore + PK decoded 227 | if ($(Get-Content -Path $PemPath) -match '-----BEGIN PRIVATE KEY-----') 228 | { 229 | [System.Security.Cryptography.X509Certificates.X509Certificate2]::CreateFromPemFile($(Get-Item -Path $PemPath)) # Make sure private key is not encrypted! 230 | break 231 | } 232 | else 233 | { 234 | throw "Make sure you're private key is not encrypted" 235 | } 236 | } 237 | } 238 | 239 | 'KeyVaultCertificatePath' 240 | { 241 | if ($ExportPrivateKey) 242 | { 243 | $CertInfo = Get-KVCertificateWithPrivateKey -KeyVaultCertificatePath $KeyVaultCertificatePath -AccessToken $AccessToken -APIVersion $APIVersion 244 | $pfxUnprotectedBytes = [Convert]::FromBase64String($CertInfo.value) 245 | [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($pfxUnprotectedBytes) 246 | } 247 | else 248 | { 249 | $CertInfo = Get-KVCertificateWithPublicKey -KeyVaultCertificatePath $KeyVaultCertificatePath -AccessToken $AccessToken -APIVersion $APIVersion 250 | if ($IsWindows) 251 | { 252 | $cBytes = [System.Text.Encoding]::UTF8.GetBytes($CertInfo.cer) 253 | [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($cBytes) 254 | } 255 | else 256 | { 257 | $ModCertInfo = @" 258 | -----BEGIN CERTIFICATE----- 259 | $($CertInfo.cer) 260 | -----END CERTIFICATE----- 261 | "@ 262 | $cBytes = [System.Text.Encoding]::UTF8.GetBytes($ModCertInfo) 263 | [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($cBytes) 264 | } 265 | } 266 | } 267 | }#end switch 268 | } #process 269 | 270 | End 271 | { 272 | Write-Verbose "[$((Get-Date).TimeofDay)] Ending $($myinvocation.mycommand)" 273 | } #end 274 | } 275 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | branches: 3 | include: 4 | - main 5 | paths: 6 | exclude: 7 | - CHANGELOG.md 8 | tags: 9 | include: 10 | - "v*" 11 | exclude: 12 | - "*-*" 13 | 14 | variables: 15 | buildFolderName: output 16 | buildArtifactName: output 17 | testResultFolderName: testResults 18 | defaultBranch: main 19 | Agent.Source.Git.ShallowFetchDepth: 0 # override ShallowFetchDepth 20 | 21 | stages: 22 | - stage: Build 23 | jobs: 24 | - job: Package_Module 25 | displayName: 'Package Module' 26 | pool: 27 | vmImage: 'ubuntu-latest' 28 | steps: 29 | - pwsh: | 30 | dotnet tool install --global GitVersion.Tool 31 | $gitVersionObject = dotnet-gitversion | ConvertFrom-Json 32 | $gitVersionObject.PSObject.Properties.ForEach{ 33 | Write-Host -Object "Setting Task Variable '$($_.Name)' with value '$($_.Value)'." 34 | Write-Host -Object "##vso[task.setvariable variable=$($_.Name);]$($_.Value)" 35 | } 36 | Write-Host -Object "##vso[build.updatebuildnumber]$($gitVersionObject.FullSemVer)" 37 | displayName: Calculate ModuleVersion (GitVersion) 38 | 39 | - task: PowerShell@2 40 | name: package 41 | displayName: 'Build & Package Module' 42 | inputs: 43 | filePath: './build.ps1' 44 | arguments: '-ResolveDependency -tasks pack' 45 | pwsh: true 46 | env: 47 | ModuleVersion: $(NuGetVersionV2) 48 | 49 | - task: PublishPipelineArtifact@1 50 | displayName: 'Publish Build Artifact' 51 | inputs: 52 | targetPath: '$(buildFolderName)/' 53 | artifact: $(buildArtifactName) 54 | publishLocation: 'pipeline' 55 | parallel: true 56 | 57 | - stage: Test 58 | dependsOn: Build 59 | jobs: 60 | - job: test_linux 61 | displayName: 'Linux' 62 | timeoutInMinutes: 0 63 | pool: 64 | vmImage: 'ubuntu-latest' 65 | steps: 66 | - task: DownloadPipelineArtifact@2 67 | displayName: 'Download Build Artifact' 68 | inputs: 69 | buildType: 'current' 70 | artifactName: $(buildArtifactName) 71 | targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)' 72 | 73 | - task: PowerShell@2 74 | name: test 75 | displayName: 'Run Tests' 76 | inputs: 77 | filePath: './build.ps1' 78 | arguments: '-tasks test' 79 | 80 | - task: PublishTestResults@2 81 | displayName: 'Publish Test Results' 82 | condition: succeededOrFailed() 83 | inputs: 84 | testResultsFormat: 'NUnit' 85 | testResultsFiles: '$(buildFolderName)/$(testResultFolderName)/NUnit*.xml' 86 | testRunTitle: 'Linux' 87 | 88 | - task: PublishPipelineArtifact@1 89 | displayName: 'Publish Test Artifact' 90 | inputs: 91 | targetPath: '$(buildFolderName)/$(testResultFolderName)/' 92 | artifactName: 'CodeCoverageLinux' 93 | parallel: true 94 | 95 | - job: test_windows_core 96 | displayName: 'Windows (PowerShell)' 97 | timeoutInMinutes: 0 98 | pool: 99 | vmImage: 'windows-latest' 100 | steps: 101 | - task: DownloadPipelineArtifact@2 102 | displayName: 'Download Build Artifact' 103 | inputs: 104 | buildType: 'current' 105 | artifactName: $(buildArtifactName) 106 | targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)' 107 | 108 | - task: PowerShell@2 109 | name: test 110 | displayName: 'Run Tests' 111 | inputs: 112 | filePath: './build.ps1' 113 | arguments: '-tasks test' 114 | pwsh: true 115 | 116 | - task: PublishTestResults@2 117 | displayName: 'Publish Test Results' 118 | condition: succeededOrFailed() 119 | inputs: 120 | testResultsFormat: 'NUnit' 121 | testResultsFiles: '$(buildFolderName)/$(testResultFolderName)/NUnit*.xml' 122 | testRunTitle: 'Windows (PowerShell)' 123 | 124 | - task: PublishPipelineArtifact@1 125 | displayName: 'Publish Test Artifact' 126 | inputs: 127 | targetPath: '$(buildFolderName)/$(testResultFolderName)/' 128 | artifactName: 'CodeCoverageWinPS7' 129 | parallel: true 130 | 131 | - job: test_windows_ps 132 | displayName: 'Windows (Windows PowerShell)' 133 | timeoutInMinutes: 0 134 | pool: 135 | vmImage: 'windows-latest' 136 | steps: 137 | - task: DownloadPipelineArtifact@2 138 | displayName: 'Download Build Artifact' 139 | inputs: 140 | buildType: 'current' 141 | artifactName: $(buildArtifactName) 142 | targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)' 143 | 144 | - task: PowerShell@2 145 | name: test 146 | displayName: 'Run Tests' 147 | inputs: 148 | filePath: './build.ps1' 149 | arguments: '-tasks test' 150 | pwsh: false 151 | 152 | - task: PublishTestResults@2 153 | displayName: 'Publish Test Results' 154 | condition: succeededOrFailed() 155 | inputs: 156 | testResultsFormat: 'NUnit' 157 | testResultsFiles: '$(buildFolderName)/$(testResultFolderName)/NUnit*.xml' 158 | testRunTitle: 'Windows (Windows PowerShell)' 159 | 160 | - task: PublishPipelineArtifact@1 161 | displayName: 'Publish Test Artifact' 162 | inputs: 163 | targetPath: '$(buildFolderName)/$(testResultFolderName)/' 164 | artifactName: 'CodeCoverageWinPS51' 165 | parallel: true 166 | 167 | - job: test_macos 168 | displayName: 'macOS' 169 | timeoutInMinutes: 0 170 | pool: 171 | vmImage: 'macos-latest' 172 | steps: 173 | - task: DownloadPipelineArtifact@2 174 | displayName: 'Download Build Artifact' 175 | inputs: 176 | buildType: 'current' 177 | artifactName: $(buildArtifactName) 178 | targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)' 179 | 180 | - task: PowerShell@2 181 | name: test 182 | displayName: 'Run Tests' 183 | inputs: 184 | filePath: './build.ps1' 185 | arguments: '-tasks test' 186 | pwsh: true 187 | 188 | - task: PublishTestResults@2 189 | displayName: 'Publish Test Results' 190 | condition: succeededOrFailed() 191 | inputs: 192 | testResultsFormat: 'NUnit' 193 | testResultsFiles: '$(buildFolderName)/$(testResultFolderName)/NUnit*.xml' 194 | testRunTitle: 'MacOS' 195 | 196 | - task: PublishPipelineArtifact@1 197 | displayName: 'Publish Test Artifact' 198 | inputs: 199 | targetPath: '$(buildFolderName)/$(testResultFolderName)/' 200 | artifactName: 'CodeCoverageMacOS' 201 | parallel: true 202 | 203 | # If no code coverage should be reported, then this entire removed: 204 | - job: Code_Coverage 205 | displayName: 'Publish Code Coverage' 206 | dependsOn: 207 | - test_macos 208 | - test_linux 209 | - test_windows_core 210 | - test_windows_ps 211 | pool: 212 | vmImage: 'ubuntu-latest' 213 | timeoutInMinutes: 0 214 | steps: 215 | - pwsh: | 216 | $repositoryOwner,$repositoryName = $env:BUILD_REPOSITORY_NAME -split '/' 217 | echo "##vso[task.setvariable variable=RepositoryOwner;isOutput=true]$repositoryOwner" 218 | echo "##vso[task.setvariable variable=RepositoryName;isOutput=true]$repositoryName" 219 | name: dscBuildVariable 220 | displayName: 'Set Environment Variables' 221 | 222 | - task: DownloadPipelineArtifact@2 223 | displayName: 'Download Pipeline Artifact' 224 | inputs: 225 | buildType: 'current' 226 | artifactName: $(buildArtifactName) 227 | targetPath: '$(Build.SourcesDirectory)/$(buildArtifactName)' 228 | 229 | - task: DownloadPipelineArtifact@2 230 | displayName: 'Download Test Artifact macOS' 231 | inputs: 232 | buildType: 'current' 233 | artifactName: 'CodeCoverageMacOS' 234 | targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)/$(testResultFolderName)' 235 | 236 | - task: DownloadPipelineArtifact@2 237 | displayName: 'Download Test Artifact Linux' 238 | inputs: 239 | buildType: 'current' 240 | artifactName: 'CodeCoverageLinux' 241 | targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)/$(testResultFolderName)' 242 | 243 | - task: DownloadPipelineArtifact@2 244 | displayName: 'Download Test Artifact Windows (PS 5.1)' 245 | inputs: 246 | buildType: 'current' 247 | artifactName: 'CodeCoverageWinPS51' 248 | targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)/$(testResultFolderName)' 249 | 250 | - task: DownloadPipelineArtifact@2 251 | displayName: 'Download Test Artifact Windows (PS7)' 252 | inputs: 253 | buildType: 'current' 254 | artifactName: 'CodeCoverageWinPS7' 255 | targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)/$(testResultFolderName)' 256 | 257 | # Make sure to update build.yaml to support these tasks, then uncomment these tasks: 258 | #- task: PowerShell@2 259 | # name: merge 260 | # displayName: 'Merge Code Coverage files' 261 | # inputs: 262 | # filePath: './build.ps1' 263 | # arguments: '-tasks merge' 264 | # pwsh: true 265 | #- task: PublishCodeCoverageResults@1 266 | # displayName: 'Publish Azure Code Coverage' 267 | # inputs: 268 | # codeCoverageTool: 'JaCoCo' 269 | # summaryFileLocation: '$(buildFolderName)/$(testResultFolderName)/JaCoCo_coverage.xml' 270 | # pathToSources: '$(Build.SourcesDirectory)/$(dscBuildVariable.RepositoryName)/' 271 | 272 | # Uncomment if Codecov.io should be used (see docs at Codecov.io how to use and the required repository configuration). 273 | #- script: | 274 | # bash <(curl -s https://codecov.io/bash) -f "./$(buildFolderName)/$(testResultFolderName)/JaCoCo_coverage.xml" 275 | # displayName: 'Publish Code Coverage to Codecov.io' 276 | 277 | - stage: Deploy 278 | dependsOn: Test 279 | # Only execute deploy stage if we're on master and previous stage succeeded 280 | condition: | 281 | and( 282 | succeeded(), 283 | or( 284 | eq(variables['Build.SourceBranch'], 'refs/heads/main'), 285 | startsWith(variables['Build.SourceBranch'], 'refs/tags/') 286 | ), 287 | contains(variables['System.TeamFoundationCollectionUri'], 'MyOrgName') 288 | ) 289 | jobs: 290 | - job: Deploy_Module 291 | displayName: 'Deploy Module' 292 | pool: 293 | vmImage: 'ubuntu-latest' 294 | steps: 295 | - task: DownloadPipelineArtifact@2 296 | displayName: 'Download Build Artifact' 297 | inputs: 298 | buildType: 'current' 299 | artifactName: $(buildArtifactName) 300 | targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)' 301 | - task: PowerShell@2 302 | name: publishRelease 303 | displayName: 'Publish Release' 304 | inputs: 305 | filePath: './build.ps1' 306 | arguments: '-tasks publish' 307 | pwsh: true 308 | env: 309 | GitHubToken: $(GitHubToken) 310 | GalleryApiToken: $(GalleryApiToken) 311 | ReleaseBranch: $(defaultBranch) 312 | MainGitBranch: $(defaultBranch) 313 | - task: PowerShell@2 314 | name: sendChangelogPR 315 | displayName: 'Send Changelog PR' 316 | inputs: 317 | filePath: './build.ps1' 318 | arguments: '-tasks Create_ChangeLog_GitHub_PR' 319 | pwsh: true 320 | env: 321 | GitHubToken: $(GitHubToken) 322 | ReleaseBranch: $(defaultBranch) 323 | MainGitBranch: $(defaultBranch) 324 | 325 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PSMSALNet Module 2 | 3 | :warning: This is a **Powershel 7.4** module minimum but should work on Linux/MAC/Windows. 4 | 5 | This project wraps [MSAL.NET](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet) functionality into PowerShell-friendly cmdlets. The goal is not to implement every flows MSAL can propose but the more useful and secured ones. Flow like ROPC or windows integrated flow won't be implemented for security reasons. 6 | 7 | Why not using MSAL.PS module instead? MSAL.PS has not been updated since several months now and recently the module has been declared as an "archive" by Microsoft. On the other side, the MSAL.NET team did a wonderful job and has realeased a lot of neat features that won't be implemented in MSAL.PS. 8 | 9 | Over the years, I've created a lot of identity scripts you can find in this Github account to interact with managed identities, Azure ARC for server or federated credentials. Rencently I've decided to create this module (mainly for my needs) because all those flows are now included in MSAL.NET which will simplify a lot of things :smiley:. 10 | 11 | Thanks to MSAL.NET, I've discovered WAM (Web Account Manager) wich is a Windows feature compatible with modern authentication and more specificaly with MFA (compared to Windows Integrated flow)! The problem that I've discovered is that Powershell is not compatible with the library regarding WAM (at least I've stop after 20 hours of tries) this is why I've decided to try in C# directly. The funny thing is that I'm a newbie in C#. 12 | 13 | Talking about libraries, this module rely a lot on various MSAL libraries: 14 | - Microsoft.Identity.Client -> Core 15 | - Microsoft.IdentityModel.Abstractions -> Core dependency 16 | - Microsoft.Identity.Client.Broker > required for WAM 17 | - Microsoft.Identity.Client.NativeInterop > required for WAM 18 | - Microsoft.Identity.Client.Extensions.Msal > to serialize tokens on the local disks (in future version) 19 | 20 | In addition, I created two other libraries for device code and WAM: 21 | - [DeviceCodeHelper](https://github.com/SCOMnewbie/DeviceCodeHelper) -> Used for device code flow (no really?) 22 | - [WAMHelper](https://github.com/SCOMnewbie/WAMHelper) 23 | 24 | This module won't focus exclusively on the MSAL library but will add features around identity concepts in general to help us to better consume Entra features. 25 | 26 | This module will feat perfectly with the [ValidateAADJWt](https://www.powershellgallery.com/packages/ValidateAADJwt) (maybe I should change the module name lol) to validate all tokens you will generate with this module. 27 | 28 | ## What you can do with this module? 29 | 30 | ### Generate Entra tokens with various flows (Get-EntraToken) 31 | 32 | - Client credential flow with both secret and certificate for machine to machine communication (application context). 33 | - Public Authorization Code with PKCE (human context). 34 | - Device Code flow for headless Operating system. It's preferable to use the authorization code with PKCE instead (human context). 35 | - Windows Account Manager flow (human context). 36 | - On behalf flow (OBO) with both secret and certificate for your backend api (human context). 37 | - System Managed identity from anywhere even Azure ARC for server (application context)! 38 | - User Managed identity (application context). 39 | - Federated credential 40 | 41 | To help you in this complex subject, this module will re-use ideas that I've implemented in other scripts. Several resources are pre-defined (Graph API, KeyVault, Storage, ARM...) to help you to find the proper resource. In addition, for user context only (application context is auto completed) you will have to define all the permissions you need. Check examples to better understand. 42 | 43 | ### Generate X509 certificate objects (ConvertTo-X509Certificate2, Get-KVCertificateWithPrivateKey, Get-KVCertificateWithPublicKey) 44 | 45 | Using certificate in both Windows and Linux can become a pain quickly. This module will help you to exposed X509 certificate objects you will then consume with Get-EntraToken cmdlet. This function will propose several certifacte format/source: 46 | 47 | - **Certificate Type**: 48 | - Pfx 49 | - Pem 50 | - Crt 51 | - Cer 52 | - **Source**: 53 | - Local disk 54 | - Azure Keyvault (JWT token required) 55 | 56 | ## What will come next? 57 | 58 | - Improve unit testing 59 | - Improve documentation 60 | - Implement local token serialization for public appilcations (flows that don't need secret/cert/assertion) 61 | 62 | ## How to use it 63 | 64 | ### Client credential flow 65 | 66 | #### With secret 67 | 68 | This command will generate a token and sotre it in memory (linked to the Pwsh process). MSAL will manage the expiration of the token and call Entra when it will be necessary. 69 | 70 | ```Powershell 71 | $HashArguments = @{ 72 | ClientId = "47077650-52a9-4bc2-b689-b50002b764ee" 73 | ClientSecret = $ClientSecret 74 | TenantId = $TenantId 75 | Resource = 'GraphAPI' 76 | } 77 | Get-EntraToken -ClientCredentialFlowWithSecret @HashArguments 78 | ``` 79 | 80 | If you want to force refresh, use instead: 81 | 82 | ```Powershell 83 | $HashArguments = @{ 84 | ClientId = "47077650-52a9-4bc2-b689-b50002b764ee" 85 | ClientSecret = $ClientSecret 86 | TenantId = $TenantId 87 | Resource = 'ARM' 88 | WithoutCaching = $true 89 | } 90 | Get-EntraToken -ClientCredentialFlowWithSecret @HashArguments 91 | ``` 92 | 93 | In this case, MSAL will generate a new token to access the Azure Resource Manager resource everytime. 94 | 95 | #### With certificate 96 | 97 | From a windows, we can do: 98 | 99 | ```Powershell 100 | #https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-create-self-signed-certificate 101 | $certname = "newcert" 102 | $cert = New-SelfSignedCertificate -Subject "CN=$certname" -CertStoreLocation "Cert:\CurrentUser\My" -KeyExportPolicy Exportable -KeySpec Signature -KeyLength 2048 -KeyAlgorithm RSA -HashAlgorithm SHA256 103 | $mypwd = ConvertTo-SecureString -String "{myPassword}" -Force -AsPlainText 104 | # This is what you keep 105 | Export-PfxCertificate -Cert $cert -FilePath "C:\TEMP\$certname.pfx" -Password $mypwd 106 | # This is what you will upload to Azure app registration 107 | Export-Certificate -Cert $cert -FilePath "C:\TEMP\$certname.cer" 108 | # Generate the X509 object 109 | $X509 = ConvertTo-X509Certificate2 -PfxPath C:\TEMP\newcert.pfx -Password $(ConvertTo-SecureString -String '{myPassword}' -AsPlainText -Force) -Verbose 110 | 111 | $HashArguments = @{ 112 | ClientId = "47048650-52a9-4bc2-b689-b50002a764ee" 113 | ClientCertificate = $X509 114 | TenantId = $TenantId 115 | Resource = 'Keyvault' 116 | verbose = $true 117 | } 118 | 119 | Get-EntraToken -ClientCredentialFlowWithCertificate @HashArguments 120 | ``` 121 | This will generate a token but this time with a certificate instead of a secret. This is a more secure solution, you know you won't see certificate information in proxy/firewall/logs. 122 | Always try to use certificate compared to secrets. 123 | 124 | ### Authorization code with PKCE 125 | 126 | Imagine you want to access a resource protected by Entra and only selected person can access the resource. Because this is a public flow (in this case), no secret will be required because the user context itself is the "secret". 127 | 128 | ```Powershell 129 | 130 | $HashArguments = @{ 131 | ClientId = "4adbb0ff-3cde-4fc1-b22e-94ee7d16d70b" 132 | TenantId = $TenantId 133 | RedirectUri = 'http://localhost' 134 | Resource = 'GraphAPI' 135 | Permissions = @('user.read','group.read.all') 136 | ExtraScopesToConsent = @('https://management.azure.com/user_impersonation') 137 | verbose = $true 138 | } 139 | 140 | Get-EntraToken -PublicAuthorizationCodeFlow @HashArguments 141 | ``` 142 | 143 | Thanks to the ExtraScopesToConsent parameter, if you now type: 144 | 145 | ```Powershell 146 | 147 | $HashArguments = @{ 148 | ClientId = "4adbb0ff-3cde-4fc1-b22e-94ee7d16d70b" 149 | TenantId = $TenantId 150 | RedirectUri = 'http://localhost' 151 | Resource = 'ARM' 152 | Permissions = @('user_impersonation') 153 | verbose = $true 154 | } 155 | 156 | Get-EntraToken -PublicAuthorizationCodeFlow @HashArguments 157 | ``` 158 | 159 | You will hit the MSAL cache and won't have another windows for sign-in. To summarize, the first cmdlet requests a token to access Graph API with specific permissions and requests in parallel a token to access the Azure Resource Manager resource to avoid a second popup. 160 | 161 | ### Device code 162 | 163 | Imagine now you're on WSL/Linux (headless Operating System) but you want to access a protected resource. This is where device code can be interesting. 164 | 165 | :warning: This flow can be considered as less secure than the other flows. Don't forget to enabled the device code flow in your app registration and this flow won't be compatible with device compliant state. 166 | 167 | ```Powershell 168 | Get-EntraToken -DeviceCodeFlow -ClientId $ClientId -TenantId $TenantId -Resource GraphAPI -Permissions @('user.read') 169 | ``` 170 | 171 | This command will use the default redirect uri which is 'http://localhost'. 172 | 173 | ### Web Account Manager (WAM) 174 | 175 | WAM flow connects you device you're using with an Entra tenant. Here for exemple, I'm asking a new token to access a custom API protected by Entra: 176 | 177 | :notes: WAM seems to be the more secure way because this flow is compatible with the [token protection](https://learn.microsoft.com/en-us/azure/active-directory/conditional-access/concept-token-protection) conditional access. In addition, if your machine is already Azure joined, you won't even have to enroll it and you will be able to generate tokens straight away. 178 | 179 | ```Powershell 180 | Get-EntraToken -WAMFlow -ClientId $clientId -TenantId $tenantId -RedirectUri 'ms-appx-web://Microsoft.AAD.BrokerPlugin/9f0...8f01' -Resource Custom -CustomResource api://AADToken-WebAPI-back-OBO -Permissions access_asuser 181 | ``` 182 | 183 | Once you execute the command, you should see: 184 | 185 | ![Diagram](./images/wam01.jpg) 186 | 187 | Once you hit OK, the device enrollment should start: 188 | 189 | ![Diagram](./images/wam02.jpg) 190 | 191 | And after few seconds, you should see: 192 | 193 | ![Diagram](./images/wam03.jpg) 194 | 195 | Now if you go on your tenant, you should see a new device linked to your account and in parallel, you should receive your access/id tokens. 196 | 197 | If you re-execute the command, you will hit the MSAL cache. 198 | 199 | ### On Behalf flow (OBO) 200 | 201 | This flow is used when from a backend API (or a web site) you want to re-use the user context to access another API. Here the Microsoft diagram regarding this flow: 202 | 203 | ![Diagram](./images/obo.png) 204 | 205 | To make it works, you first have to generate a token to access a custom protected exposed API and then, from this backend API, you will be able to call another API but with the user context. Let's generate a token from a client using one of the previous public flows (authorization code, device code, WAM). 206 | 207 | ```Powershell 208 | $HashArguments = @{ 209 | ClientId = "0a74a6ca-489b-4e81-acdb-bd91db65b239" #Frontend client 210 | TenantId = $TenantId 211 | Resource = 'Custom' 212 | CustomResource = 'api://5449c592-36c4-47ca-8b02-d9813f4bf2d1' 213 | Permissions = 'access_as_user' 214 | verbose = $true 215 | } 216 | 217 | $FrontEndClientToken = Get-EntraToken -PublicAuthorizationCodeFlow @HashArguments 218 | ``` 219 | 220 | #### With secret 221 | 222 | Let's now imagine we're on the backed API this time and we will receive the access token previously generated. The backend API has Graph API permission to read user information. As you can see the previous custom resource become the client this time: 223 | 224 | :notes: Don't forget to admin consent the backend api permission even for non admin permissions. 225 | 226 | ```Powershell 227 | 228 | Get-EntraToken -OnBehalfFlowWithSecret -ClientId $BackendClientId -ClientSecret '20n...B' -TenantId $tenantId -Resource GraphAPI -Permissions 'User.read' -UserAssertion $FrontEndClientToken.accesstoken | % AccessToken 229 | ``` 230 | 231 | If you now copy this new token in JWT.ms, you will see this is a Graph API token under the user context: 232 | 233 | ![Diagram](./images/obo01.jpg) 234 | 235 | :warning: This is where the module [ValidateAADJWt](https://www.powershellgallery.com/packages/ValidateAADJwt) starts to shine. Your backend API must validate the token you receive. 236 | 237 | You can find more information on this flow [here](https://learn.microsoft.com/fr-fr/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow). 238 | 239 | #### With certificate 240 | 241 | If we re-use the previous generated certificate: 242 | 243 | ```Powershell 244 | $X509 = ConvertTo-X509Certificate2 -PfxPath C:\TEMP\newcert.pfx -Password $(ConvertTo-SecureString -String '{myPassword}' -AsPlainText -Force) -Verbose 245 | Get-EntraToken -OnBehalfFlowWithCertificate -ClientCertificate $X509 -UserAssertion $FrontEndClientToken.accesstoken -ClientId $BackendClientId -TenantId $tenantId -Resource GraphAPI -Permissions 'User.read' | % AccessToken 246 | ``` 247 | 248 | Like with the secret flow, you will receive the same token. 249 | 250 | ### System Managed Identity 251 | 252 | #### From a Windows Azure VM (tested) 253 | 254 | On a fresh new Azure VM, install Powershell 7 and then type: 255 | 256 | ```Powershell 257 | install-module PSMSALNet 258 | Get-EntraToken -SystemManagedIdentity -Resource GraphAPI 259 | ``` 260 | 261 | #### From a Linux Azure VM (tested) 262 | 263 | Once Powershell 7 installed, run: 264 | 265 | ```Powershell 266 | install-module PSMSALNet 267 | Get-EntraToken -SystemManagedIdentity -Resource GraphAPI 268 | ``` 269 | 270 | #### From a Windows Azure function (tested) 271 | 272 | Once the PSMSALNet module added to your requirements.psd1, run: 273 | 274 | ```Powershell 275 | Get-EntraToken -SystemManagedIdentity -Resource GraphAPI 276 | ``` 277 | 278 | #### From a Linux Azure function (tested) 279 | 280 | Once the PSMSALNet module added to your requirements.psd1, run: 281 | 282 | ```Powershell 283 | Get-EntraToken -SystemManagedIdentity -Resource GraphAPI 284 | ``` 285 | 286 | #### From Azure Container Instance (tested) 287 | 288 | Once the container built with the Dockerfile (check the deploy.ps1 script) in the examples folder, deploy a new ACI and enable the system MSI. And again, same command to get your access token. 289 | 290 | ![Diagram](./images/aci.jpg) 291 | 292 | #### From Windows ARC for server (tested) 293 | 294 | Once you've installed Powershell7, PSMSALNet module and installed (enrolled) the ARC agent, you run the same cmdlet again and you should receive a new token 295 | 296 | ```Powershell 297 | Get-EntraToken -SystemManagedIdentity -Resource GraphAPI 298 | ``` 299 | 300 | #### From Linux ARC for server (tested) 301 | 302 | :warning: Does not work for now, for now use this [script](https://github.com/SCOMnewbie/Azure/blob/master/Identity-AAD/Get-AccessTokenWithAzIdentity.ps1) instead. 303 | 304 | Error received: 305 | 306 | ![Diagram](./images/linuxarcerror.jpg) 307 | 308 | Bug declared in [MSAL.net github repository](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/4358). 309 | 310 | **Update:** In 0.0.7 a temporary fix has been implemented to generate a token withoit relying on MSAL until the MSAL.Net fix the issue. 311 | 312 | ### Federated credentials 313 | 314 | #### From a managed or non managed **Kubernetes cluster** 315 | 316 | You can find the documentation under the Examples\aks-workloadidentity folder. 317 | 318 | The highlights are: 319 | 320 | - Create the app registration that will represent your pod(s) 321 | - Create the Kube cluster like AKS 322 | - Install the workload identity part with Helm 323 | - Create and ACR and make it build you container, then connect your Kube cluster to your ACR 324 | - Deploy your Pod, check the pod logs and you should see your container can now request for it's own tokens. 325 | 326 | App registration configuration: 327 | 328 | ![Diagram](./images/federatedcreds.jpg) 329 | 330 | ```Powershell 331 | $KubeSaToken = Get-Content -Path '/var/run/secrets/azure/tokens/azure-identity-token' 332 | Get-EntraToken -FederatedCredentialFlowWithAssertion -UserAssertion $KubeSaToken -ClientId $([Environment]::GetEnvironmentVariable('AZURE_CLIENT_ID')) -TenantId $([Environment]::GetEnvironmentVariable('AZURE_TENANT_ID')) -Resource GraphAPI 333 | ``` 334 | 335 | ## How to contribute 336 | 337 | This module is based on Sampler module. To contribute, clone the repo and run a .\build.ps1 -Task build -ResolveDependency 338 | -------------------------------------------------------------------------------- /Resolve-Dependency.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .DESCRIPTION 3 | Bootstrap script for PSDepend. 4 | 5 | .PARAMETER DependencyFile 6 | Specifies the configuration file for the this script. The default value is 7 | 'RequiredModules.psd1' relative to this script's path. 8 | 9 | .PARAMETER PSDependTarget 10 | Path for PSDepend to be bootstrapped and save other dependencies. 11 | Can also be CurrentUser or AllUsers if you wish to install the modules in 12 | such scope. The default value is 'output/RequiredModules' relative to 13 | this script's path. 14 | 15 | .PARAMETER Proxy 16 | Specifies the URI to use for Proxy when attempting to bootstrap 17 | PackageProvider and PowerShellGet. 18 | 19 | .PARAMETER ProxyCredential 20 | Specifies the credential to contact the Proxy when provided. 21 | 22 | .PARAMETER Scope 23 | Specifies the scope to bootstrap the PackageProvider and PSGet if not available. 24 | THe default value is 'CurrentUser'. 25 | 26 | .PARAMETER Gallery 27 | Specifies the gallery to use when bootstrapping PackageProvider, PSGet and 28 | when calling PSDepend (can be overridden in Dependency files). The default 29 | value is 'PSGallery'. 30 | 31 | .PARAMETER GalleryCredential 32 | Specifies the credentials to use with the Gallery specified above. 33 | 34 | .PARAMETER AllowOldPowerShellGetModule 35 | Allow you to use a locally installed version of PowerShellGet older than 36 | 1.6.0 (not recommended). Default it will install the latest PowerShellGet 37 | if an older version than 2.0 is detected. 38 | 39 | .PARAMETER MinimumPSDependVersion 40 | Allow you to specify a minimum version fo PSDepend, if you're after specific 41 | features. 42 | 43 | .PARAMETER AllowPrerelease 44 | Not yet written. 45 | 46 | .PARAMETER WithYAML 47 | Not yet written. 48 | 49 | .NOTES 50 | Load defaults for parameters values from Resolve-Dependency.psd1 if not 51 | provided as parameter. 52 | #> 53 | [CmdletBinding()] 54 | param 55 | ( 56 | [Parameter()] 57 | [System.String] 58 | $DependencyFile = 'RequiredModules.psd1', 59 | 60 | [Parameter()] 61 | [System.String] 62 | $PSDependTarget = (Join-Path -Path $PSScriptRoot -ChildPath 'output/RequiredModules'), 63 | 64 | [Parameter()] 65 | [System.Uri] 66 | $Proxy, 67 | 68 | [Parameter()] 69 | [System.Management.Automation.PSCredential] 70 | $ProxyCredential, 71 | 72 | [Parameter()] 73 | [ValidateSet('CurrentUser', 'AllUsers')] 74 | [System.String] 75 | $Scope = 'CurrentUser', 76 | 77 | [Parameter()] 78 | [System.String] 79 | $Gallery = 'PSGallery', 80 | 81 | [Parameter()] 82 | [System.Management.Automation.PSCredential] 83 | $GalleryCredential, 84 | 85 | [Parameter()] 86 | [System.Management.Automation.SwitchParameter] 87 | $AllowOldPowerShellGetModule, 88 | 89 | [Parameter()] 90 | [System.String] 91 | $MinimumPSDependVersion, 92 | 93 | [Parameter()] 94 | [System.Management.Automation.SwitchParameter] 95 | $AllowPrerelease, 96 | 97 | [Parameter()] 98 | [System.Management.Automation.SwitchParameter] 99 | $WithYAML, 100 | 101 | [Parameter()] 102 | [System.Collections.Hashtable] 103 | $RegisterGallery 104 | ) 105 | 106 | try 107 | { 108 | if ($PSVersionTable.PSVersion.Major -le 5) 109 | { 110 | if (-not (Get-Command -Name 'Import-PowerShellDataFile' -ErrorAction 'SilentlyContinue')) 111 | { 112 | Import-Module -Name Microsoft.PowerShell.Utility -RequiredVersion '3.1.0.0' 113 | } 114 | 115 | <# 116 | Making sure the imported PackageManagement module is not from PS7 module 117 | path. The VSCode PS extension is changing the $env:PSModulePath and 118 | prioritize the PS7 path. This is an issue with PowerShellGet because 119 | it loads an old version if available (or fail to load latest). 120 | #> 121 | Get-Module -ListAvailable PackageManagement | 122 | Where-Object -Property 'ModuleBase' -NotMatch 'powershell.7' | 123 | Select-Object -First 1 | 124 | Import-Module -Force 125 | } 126 | 127 | Write-Verbose -Message 'Importing Bootstrap default parameters from ''$PSScriptRoot/Resolve-Dependency.psd1''.' 128 | 129 | $resolveDependencyConfigPath = Join-Path -Path $PSScriptRoot -ChildPath '.\Resolve-Dependency.psd1' -Resolve -ErrorAction 'Stop' 130 | 131 | $resolveDependencyDefaults = Import-PowerShellDataFile -Path $resolveDependencyConfigPath 132 | 133 | $parameterToDefault = $MyInvocation.MyCommand.ParameterSets.Where{ $_.Name -eq $PSCmdlet.ParameterSetName }.Parameters.Keys 134 | 135 | if ($parameterToDefault.Count -eq 0) 136 | { 137 | $parameterToDefault = $MyInvocation.MyCommand.Parameters.Keys 138 | } 139 | 140 | # Set the parameters available in the Parameter Set, or it's not possible to choose yet, so all parameters are an option. 141 | foreach ($parameterName in $parameterToDefault) 142 | { 143 | if (-not $PSBoundParameters.Keys.Contains($parameterName) -and $resolveDependencyDefaults.ContainsKey($parameterName)) 144 | { 145 | Write-Verbose -Message "Setting parameter '$parameterName' to value '$($resolveDependencyDefaults[$parameterName])'." 146 | 147 | try 148 | { 149 | $variableValue = $resolveDependencyDefaults[$parameterName] 150 | 151 | if ($variableValue -is [System.String]) 152 | { 153 | $variableValue = $ExecutionContext.InvokeCommand.ExpandString($variableValue) 154 | } 155 | 156 | $PSBoundParameters.Add($parameterName, $variableValue) 157 | 158 | Set-Variable -Name $parameterName -Value $variableValue -Force -ErrorAction 'SilentlyContinue' 159 | } 160 | catch 161 | { 162 | Write-Verbose -Message "Error adding default for $parameterName : $($_.Exception.Message)." 163 | } 164 | } 165 | } 166 | } 167 | catch 168 | { 169 | Write-Warning -Message "Error attempting to import Bootstrap's default parameters from '$resolveDependencyConfigPath': $($_.Exception.Message)." 170 | } 171 | 172 | Write-Progress -Activity 'Bootstrap:' -PercentComplete 0 -CurrentOperation 'NuGet Bootstrap' 173 | 174 | $importModuleParameters = @{ 175 | Name = 'PowerShellGet' 176 | MinimumVersion = '2.0' 177 | ErrorAction = 'SilentlyContinue' 178 | PassThru = $true 179 | } 180 | 181 | if ($AllowOldPowerShellGetModule) 182 | { 183 | $importModuleParameters.Remove('MinimumVersion') 184 | } 185 | 186 | $powerShellGetModule = Import-Module @importModuleParameters 187 | 188 | # Install the package provider if it is not available. 189 | $nuGetProvider = Get-PackageProvider -Name 'NuGet' -ListAvailable -ErrorAction 'SilentlyContinue' | 190 | Select-Object -First 1 191 | 192 | if (-not $powerShellGetModule -and -not $nuGetProvider) 193 | { 194 | $providerBootstrapParameters = @{ 195 | Name = 'NuGet' 196 | Force = $true 197 | ForceBootstrap = $true 198 | ErrorAction = 'Stop' 199 | Scope = $Scope 200 | } 201 | 202 | switch ($PSBoundParameters.Keys) 203 | { 204 | 'Proxy' 205 | { 206 | $providerBootstrapParameters.Add('Proxy', $Proxy) 207 | } 208 | 209 | 'ProxyCredential' 210 | { 211 | $providerBootstrapParameters.Add('ProxyCredential', $ProxyCredential) 212 | } 213 | 214 | 'AllowPrerelease' 215 | { 216 | $providerBootstrapParameters.Add('AllowPrerelease', $AllowPrerelease) 217 | } 218 | } 219 | 220 | Write-Information -MessageData 'Bootstrap: Installing NuGet Package Provider from the web (Make sure Microsoft addresses/ranges are allowed).' 221 | 222 | $null = Install-PackageProvider @providerBootstrapParameters 223 | 224 | $nuGetProvider = Get-PackageProvider -Name 'NuGet' -ListAvailable | Select-Object -First 1 225 | 226 | $nuGetProviderVersion = $nuGetProvider.Version.ToString() 227 | 228 | Write-Information -MessageData "Bootstrap: Importing NuGet Package Provider version $nuGetProviderVersion to current session." 229 | 230 | $Null = Import-PackageProvider -Name 'NuGet' -RequiredVersion $nuGetProviderVersion -Force 231 | } 232 | 233 | if ($RegisterGallery) 234 | { 235 | if ($RegisterGallery.ContainsKey('Name') -and -not [System.String]::IsNullOrEmpty($RegisterGallery.Name)) 236 | { 237 | $Gallery = $RegisterGallery.Name 238 | } 239 | else 240 | { 241 | $RegisterGallery.Name = $Gallery 242 | } 243 | 244 | Write-Progress -Activity 'Bootstrap:' -PercentComplete 7 -CurrentOperation "Verifying private package repository '$Gallery'" -Completed 245 | 246 | $previousRegisteredRepository = Get-PSRepository -Name $Gallery -ErrorAction 'SilentlyContinue' 247 | 248 | if ($previousRegisteredRepository.SourceLocation -ne $RegisterGallery.SourceLocation) 249 | { 250 | if ($previousRegisteredRepository) 251 | { 252 | Write-Progress -Activity 'Bootstrap:' -PercentComplete 9 -CurrentOperation "Re-registrering private package repository '$Gallery'" -Completed 253 | 254 | Unregister-PSRepository -Name $Gallery 255 | 256 | $unregisteredPreviousRepository = $true 257 | } 258 | else 259 | { 260 | Write-Progress -Activity 'Bootstrap:' -PercentComplete 9 -CurrentOperation "Registering private package repository '$Gallery'" -Completed 261 | } 262 | 263 | Register-PSRepository @RegisterGallery 264 | } 265 | } 266 | 267 | Write-Progress -Activity 'Bootstrap:' -PercentComplete 10 -CurrentOperation "Ensuring Gallery $Gallery is trusted" 268 | 269 | # Fail if the given PSGallery is not registered. 270 | $previousGalleryInstallationPolicy = (Get-PSRepository -Name $Gallery -ErrorAction 'Stop').InstallationPolicy 271 | 272 | if ($previousGalleryInstallationPolicy -ne 'Trusted') 273 | { 274 | # Only change policy if the repository is not trusted 275 | Set-PSRepository -Name $Gallery -InstallationPolicy 'Trusted' -ErrorAction 'Ignore' 276 | } 277 | 278 | try 279 | { 280 | Write-Progress -Activity 'Bootstrap:' -PercentComplete 25 -CurrentOperation 'Checking PowerShellGet' 281 | 282 | # Ensure the module is loaded and retrieve the version you have. 283 | $powerShellGetVersion = (Import-Module -Name 'PowerShellGet' -PassThru -ErrorAction 'SilentlyContinue').Version 284 | 285 | Write-Verbose -Message "Bootstrap: The PowerShellGet version is $powerShellGetVersion" 286 | 287 | # Versions below 2.0 are considered old, unreliable & not recommended 288 | if (-not $powerShellGetVersion -or ($powerShellGetVersion -lt [System.Version] '2.0' -and -not $AllowOldPowerShellGetModule)) 289 | { 290 | Write-Progress -Activity 'Bootstrap:' -PercentComplete 40 -CurrentOperation 'Fetching newer version of PowerShellGet' 291 | 292 | # PowerShellGet module not found, installing or saving it. 293 | if ($PSDependTarget -in 'CurrentUser', 'AllUsers') 294 | { 295 | Write-Debug -Message "PowerShellGet module not found. Attempting to install from Gallery $Gallery." 296 | 297 | Write-Warning -Message "Installing PowerShellGet in $PSDependTarget Scope." 298 | 299 | $installPowerShellGetParameters = @{ 300 | Name = 'PowerShellGet' 301 | Force = $true 302 | SkipPublisherCheck = $true 303 | AllowClobber = $true 304 | Scope = $Scope 305 | Repository = $Gallery 306 | } 307 | 308 | switch ($PSBoundParameters.Keys) 309 | { 310 | 'Proxy' 311 | { 312 | $installPowerShellGetParameters.Add('Proxy', $Proxy) 313 | } 314 | 315 | 'ProxyCredential' 316 | { 317 | $installPowerShellGetParameters.Add('ProxyCredential', $ProxyCredential) 318 | } 319 | 320 | 'GalleryCredential' 321 | { 322 | $installPowerShellGetParameters.Add('Credential', $GalleryCredential) 323 | } 324 | } 325 | 326 | Write-Progress -Activity 'Bootstrap:' -PercentComplete 60 -CurrentOperation 'Installing newer version of PowerShellGet' 327 | 328 | Install-Module @installPowerShellGetParameters 329 | } 330 | else 331 | { 332 | Write-Debug -Message "PowerShellGet module not found. Attempting to Save from Gallery $Gallery to $PSDependTarget" 333 | 334 | $saveModuleParameters = @{ 335 | Name = 'PowerShellGet' 336 | Repository = $Gallery 337 | Path = $PSDependTarget 338 | Force = $true 339 | } 340 | 341 | Write-Progress -Activity 'Bootstrap:' -PercentComplete 60 -CurrentOperation "Saving PowerShellGet from $Gallery to $Scope" 342 | 343 | Save-Module @saveModuleParameters 344 | } 345 | 346 | Write-Debug -Message 'Removing previous versions of PowerShellGet and PackageManagement from session' 347 | 348 | Get-Module -Name 'PowerShellGet' -All | Remove-Module -Force -ErrorAction 'SilentlyContinue' 349 | Get-Module -Name 'PackageManagement' -All | Remove-Module -Force 350 | 351 | Write-Progress -Activity 'Bootstrap:' -PercentComplete 65 -CurrentOperation 'Loading latest version of PowerShellGet' 352 | 353 | Write-Debug -Message 'Importing latest PowerShellGet and PackageManagement versions into session' 354 | 355 | if ($AllowOldPowerShellGetModule) 356 | { 357 | $powerShellGetModule = Import-Module -Name 'PowerShellGet' -Force -PassThru 358 | } 359 | else 360 | { 361 | Import-Module -Name 'PackageManagement' -MinimumVersion '1.4.8.1' -Force 362 | 363 | $powerShellGetModule = Import-Module -Name 'PowerShellGet' -MinimumVersion '2.2.5' -Force -PassThru 364 | } 365 | 366 | $powerShellGetVersion = $powerShellGetModule.Version.ToString() 367 | 368 | Write-Information -MessageData "Bootstrap: PowerShellGet version loaded is $powerShellGetVersion" 369 | } 370 | 371 | # Try to import the PSDepend module from the available modules. 372 | $getModuleParameters = @{ 373 | Name = 'PSDepend' 374 | ListAvailable = $true 375 | } 376 | 377 | $psDependModule = Get-Module @getModuleParameters 378 | 379 | if ($PSBoundParameters.ContainsKey('MinimumPSDependVersion')) 380 | { 381 | try 382 | { 383 | $psDependModule = $psDependModule | Where-Object -FilterScript { $_.Version -ge $MinimumPSDependVersion } 384 | } 385 | catch 386 | { 387 | throw ('There was a problem finding the minimum version of PSDepend. Error: {0}' -f $_) 388 | } 389 | } 390 | 391 | if (-not $psDependModule) 392 | { 393 | # PSDepend module not found, installing or saving it. 394 | if ($PSDependTarget -in 'CurrentUser', 'AllUsers') 395 | { 396 | Write-Debug -Message "PSDepend module not found. Attempting to install from Gallery '$Gallery'." 397 | 398 | Write-Warning -Message "Installing PSDepend in $PSDependTarget Scope." 399 | 400 | $installPSDependParameters = @{ 401 | Name = 'PSDepend' 402 | Repository = $Gallery 403 | Force = $true 404 | Scope = $PSDependTarget 405 | SkipPublisherCheck = $true 406 | AllowClobber = $true 407 | } 408 | 409 | if ($MinimumPSDependVersion) 410 | { 411 | $installPSDependParameters.Add('MinimumVersion', $MinimumPSDependVersion) 412 | } 413 | 414 | Write-Progress -Activity 'Bootstrap:' -PercentComplete 75 -CurrentOperation "Installing PSDepend from $Gallery" 415 | 416 | Install-Module @installPSDependParameters 417 | } 418 | else 419 | { 420 | Write-Debug -Message "PSDepend module not found. Attempting to Save from Gallery $Gallery to $PSDependTarget" 421 | 422 | $saveModuleParameters = @{ 423 | Name = 'PSDepend' 424 | Repository = $Gallery 425 | Path = $PSDependTarget 426 | Force = $true 427 | } 428 | 429 | if ($MinimumPSDependVersion) 430 | { 431 | $saveModuleParameters.add('MinimumVersion', $MinimumPSDependVersion) 432 | } 433 | 434 | Write-Progress -Activity 'Bootstrap:' -PercentComplete 75 -CurrentOperation "Saving PSDepend from $Gallery to $Scope" 435 | 436 | Save-Module @saveModuleParameters 437 | } 438 | } 439 | 440 | Write-Progress -Activity 'Bootstrap:' -PercentComplete 80 -CurrentOperation 'Loading PSDepend' 441 | 442 | $importModulePSDependParameters = @{ 443 | Name = 'PSDepend' 444 | ErrorAction = 'Stop' 445 | Force = $true 446 | } 447 | 448 | if ($PSBoundParameters.ContainsKey('MinimumPSDependVersion')) 449 | { 450 | $importModulePSDependParameters.Add('MinimumVersion', $MinimumPSDependVersion) 451 | } 452 | 453 | # We should have successfully bootstrapped PSDepend. Fail if not available. 454 | $null = Import-Module @importModulePSDependParameters 455 | 456 | if ($WithYAML) 457 | { 458 | Write-Progress -Activity 'Bootstrap:' -PercentComplete 82 -CurrentOperation 'Verifying PowerShell module PowerShell-Yaml' 459 | 460 | if (-not (Get-Module -ListAvailable -Name 'PowerShell-Yaml')) 461 | { 462 | Write-Progress -Activity 'Bootstrap:' -PercentComplete 85 -CurrentOperation 'Installing PowerShell module PowerShell-Yaml' 463 | 464 | Write-Verbose -Message "PowerShell-Yaml module not found. Attempting to Save from Gallery '$Gallery' to '$PSDependTarget'." 465 | 466 | $SaveModuleParam = @{ 467 | Name = 'PowerShell-Yaml' 468 | Repository = $Gallery 469 | Path = $PSDependTarget 470 | Force = $true 471 | } 472 | 473 | Save-Module @SaveModuleParam 474 | } 475 | else 476 | { 477 | Write-Verbose -Message 'PowerShell-Yaml is already available' 478 | } 479 | 480 | Write-Progress -Activity 'Bootstrap:' -PercentComplete 88 -CurrentOperation 'Importing PowerShell module PowerShell-Yaml' 481 | } 482 | 483 | Write-Progress -Activity 'Bootstrap:' -PercentComplete 90 -CurrentOperation 'Invoke PSDepend' 484 | 485 | Write-Progress -Activity 'PSDepend:' -PercentComplete 0 -CurrentOperation 'Restoring Build Dependencies' 486 | 487 | if (Test-Path -Path $DependencyFile) 488 | { 489 | $psDependParameters = @{ 490 | Force = $true 491 | Path = $DependencyFile 492 | } 493 | 494 | # TODO: Handle when the Dependency file is in YAML, and -WithYAML is specified. 495 | Invoke-PSDepend @psDependParameters 496 | } 497 | 498 | Write-Progress -Activity 'PSDepend:' -PercentComplete 100 -CurrentOperation 'Dependencies restored' -Completed 499 | 500 | Write-Progress -Activity 'Bootstrap:' -PercentComplete 100 -CurrentOperation 'Bootstrap complete' -Completed 501 | } 502 | finally 503 | { 504 | if ($RegisterGallery) 505 | { 506 | Write-Verbose -Message "Removing private package repository '$Gallery'." 507 | Unregister-PSRepository -Name $Gallery 508 | } 509 | 510 | if ($unregisteredPreviousRepository) 511 | { 512 | Write-Verbose -Message "Reverting private package repository '$Gallery' to previous location URI:s." 513 | 514 | $registerPSRepositoryParameters = @{ 515 | Name = $previousRegisteredRepository.Name 516 | InstallationPolicy = $previousRegisteredRepository.InstallationPolicy 517 | } 518 | 519 | if ($previousRegisteredRepository.SourceLocation) 520 | { 521 | $registerPSRepositoryParameters.SourceLocation = $previousRegisteredRepository.SourceLocation 522 | } 523 | 524 | if ($previousRegisteredRepository.PublishLocation) 525 | { 526 | $registerPSRepositoryParameters.PublishLocation = $previousRegisteredRepository.PublishLocation 527 | } 528 | 529 | if ($previousRegisteredRepository.ScriptSourceLocation) 530 | { 531 | $registerPSRepositoryParameters.ScriptSourceLocation = $previousRegisteredRepository.ScriptSourceLocation 532 | } 533 | 534 | if ($previousRegisteredRepository.ScriptPublishLocation) 535 | { 536 | $registerPSRepositoryParameters.ScriptPublishLocation = $previousRegisteredRepository.ScriptPublishLocation 537 | } 538 | 539 | Register-PSRepository @registerPSRepositoryParameters 540 | } 541 | 542 | # Only try to revert installation policy if the repository exist 543 | if ((Get-PSRepository -Name $Gallery -ErrorAction 'SilentlyContinue')) 544 | { 545 | if ($previousGalleryInstallationPolicy -and $previousGalleryInstallationPolicy -ne 'Trusted') 546 | { 547 | # Reverting the Installation Policy for the given gallery if it was not already trusted 548 | Set-PSRepository -Name $Gallery -InstallationPolicy $previousGalleryInstallationPolicy 549 | } 550 | } 551 | 552 | Write-Verbose -Message 'Project Bootstrapped, returning to Invoke-Build.' 553 | } 554 | -------------------------------------------------------------------------------- /tests/Unit/Public/ConvertTo-X509Certificate2.Tests.ps1: -------------------------------------------------------------------------------- 1 | $ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path 2 | $ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{ 3 | ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and 4 | $(try 5 | { 6 | Test-ModuleManifest $_.FullName -ErrorAction Stop 7 | } 8 | catch 9 | { 10 | $false 11 | } ) 12 | }).BaseName 13 | 14 | 15 | Import-Module $ProjectName 16 | 17 | InModuleScope $ProjectName { 18 | BeforeAll { 19 | 20 | function Get-KVCertificateWithPublicKey 21 | { 22 | } 23 | function Get-KVCertificateWithPrivateKey 24 | { 25 | } 26 | 27 | #Fake result provided by Get-KVCertificateWithPublicKey 28 | $Cer = [PSCustomObject]@{ 29 | 'id' = 'https://testvault.vault.azure.net/certificates/test/5d69153b75214245ab72fa21b9c06bfb' 30 | 'kid' = 'https://testvault.vault.azure.net/keys/test/5d69153b75214245ab72fa21b9c06bfb' 31 | 'sid' = 'https://testvault.vault.azure.net/secrets/test/5d69153b75214245ab72fa21b9c06bfb' 32 | 'x5t' = 'qRlOzJdDJeGGdmDlB63D6QK8bEQ' 33 | 'cer' = 'MIIFGzCCAwOgAwIBAgIQDlisaHyhSIeqyTk6nm6CPDANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDEwR0ZXN0MB4XDTIyMTAxMTA4NTgyMloXDTIzMTAxMTA5MDgyMlowDzENMAsGA1UEAxMEdGVzdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANPRAowbemWSKoFyC/P/bACCuU7MLdlgtk9QXszD0zm+e9YlHYfC5RcdKbqPWjQPYemZgirCBhfOgciUK/EfGIPkw6mRo2xEZjP8NoB0YBXHsu+B5KUI+mQRZpJet4hs/ndtva1/u+2CKYfGbJigJRpGIxAg1SQIW01FzuIFxpfrEYlctA4RLyH+484fiK6usw86aax120rSCv1i7gdrIgJA8ySe74d+bve5GbnE9f/zR5lA52S1Hh8t1I76F306DHCs+j5xS0x8s8RjE03o3f925fDk+LKjL9vL7rqpnkM+1sKnNdStTg0kDJ5LGfWpjQ2xzy43CCPr7IUKmOKW5MUOSymBnFAdGhGwV2MIpUD3+QCYAViPRsyx3G8xkBapE8j4nyIFVzFjXMndKu5OD8eNBqO3/ROGbk0wloTiJoRIMIZr+IlyU4IURcW2Xd8hDzCjVkqIk22C68U/ZEos+Z3I1Apdqm2DdI6NjehV1ryT8dO0Mi2pS/lMF4jBK7lvlyB3bIrzVM/ymCL9WhNnOq6LYvrcZhaoPw1THxgMWiPW7i4djsOTZQeBs+i5iHc8p4LYZTaxGkO/V4lUk9e4lAe0Bfnv3PK+Vg5DH2kLhyLrqAfZYMkA51X1OkdBePPQ2kChFZz+1AcXq2oHuToEpvm7653vVva+wHfj4q7V21CtAgMBAAGjczBxMA4GA1UdDwEB/wQEAwIEEDAJBgNVHRMEAjAAMBQGA1UdJQQNMAsGCSsGAQQBgjdQATAfBgNVHSMEGDAWgBSlVd6Gd5C7KjcrjysY5NJq61+6gjAdBgNVHQ4EFgQUpVXehneQuyo3K48rGOTSautfuoIwDQYJKoZIhvcNAQELBQADggIBAJP4Zfq85w3g80iFCYAGPzGOlWPeRavf5i2IiJPAIyr0BKOKj3M9gOiqkKHY7GW+IQLEEn5HrJ3FgJGydxbg0vhxdyrCsR0JNDT+01FEqwgyo+tswDLZSephHZXIKrGSU94iVRhJwwPCDxncDK0GtVYppQLEqxR1JOq9R5rIHJozR/TTqm9m7WK6i+9cvq1W5xiqLLe5FBerkmqpHIRSydlVDIuokkhnIN69LLfpe9XNa7N4yxxMmcNRHgeSVuo9sVoA01Ax7/Oz7XvExTOERM5JP3mJ040ye4S5tnwT3xWhHG3YkSaQ0Nlj87swVI+cpTGA0SNr/IltpzWX2C9TREXGVURfCXEADXRS/PmmNdTXPChJ43CCAI3LDBCOujWMjrZIC2MhKgVUdYJg77Q6yk8OSxEocia+18X6AOa4vAeGKa/zvDB/qoy7WlfILCyf+AquzJ1nWAq4xYoGWXk6F1pT5uKr7WA5N2ewACTS5WUG9FnfERPEn7w54yyV5nc1punCP9R9HJ2iVKDgzmibb3w/B2E8W6j9T44X9gmKykra0F1hMHhivAJFfFCCele3ISsxxT53OA+chhBAcZHa2LNIszejRkC1o96lbOWM/OBpumG3ZKSCg5bPJgqtmvA9IyjpjrG4+llp6B9AOU6iiJlYD2BUJqIQCrDhPnMcVeZ7' 34 | 'tags' = '' 35 | 'attributes' = @{ 36 | 'enabled' = 'True' 37 | 'nbf' = '1665478702' 38 | 'exp' = '1697015302' 39 | 'created' = '1665479303' 40 | 'updated' = '1665479303' 41 | 'recoveryLevel' = 'Recoverable+Purgeable' 42 | 'recoverableDays' = '90' 43 | } 44 | } 45 | 46 | $crtherestring = @' 47 | -----BEGIN CERTIFICATE----- 48 | MIIFGTCCAwGgAwIBAgIQQ62YNEfqSkm6l4EGvfydxTANBgkqhkiG9w0BAQsFADAOMQwwCgYDVQQDEwNwZW0wHhcNMjIxMDEyMTkxNzUwWhcNMjMxMDEyMTkyNzUwWjAOMQwwCgYDVQQDEwNwZW0wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDDf2UymZEDP+glV1+rjJC2xWg/U2HAu8I2dXtTr9gjWaxYNBPO0uKRv1TtvBomDp8YfYAUkHAkygnAIzSBLmc3ROogAU9efKTJlPKKlx/LBSpeEArVZI63OLq+l4ZrLHxyofFKpc6TFRKOTyHk/7wnAhMeHahuTUOrLZ0i08nbV3HhbdG9nA0cjy8MMcahhB7LIsB1WV+4M9ivM+zcYohhOEoOo02s8YGpcWakESz96tuQd9hDzJcotB0a2rGotRBYEPCZYDv0mp4kqU7Q2yOicLU/FakV5JgRUjlTRuQOq/067oe/pipcGhcdQddvjXAJIPeDVANqrr+OnYX7VkzLNOKB9vbh5EtwXPJbq4H+OCPn4F+oJsuRhBIalOC3KAxGrNb9P7WuM69LEbHZQri3LJaoY8k5g36DyEPt0usquoqPU0vu0psSSm9huHE18oE5qicnM769kpXmF7AgGEri6/e42xOFTAqwpzEGCk9x3K4izVriYQtUkYY2PF832leU9n6RDUHK993RxNhpD838RH3QiQA4mNJhWwynqTnZ1FO76403ytz/Lh0aVLjZHcsilzxaqdl49K7Dzkf6oz6JDFcqC25sMeDBX+Re1STtSddITai64KWcbrxJ9rhMXTBdNQdnOt0crIuZtIhdlhJ9ofbZWzzRUGkQw1PDIiB+xQIDAQABo3MwcTAOBgNVHQ8BAf8EBAMCBBAwCQYDVR0TBAIwADAUBgNVHSUEDTALBgkrBgEEAYI3UAEwHwYDVR0jBBgwFoAUvOqKEGPY66QcR7FMXj9M7MZ2rWcwHQYDVR0OBBYEFLzqihBj2OukHEexTF4/TOzGdq1nMA0GCSqGSIb3DQEBCwUAA4ICAQADVWgPLBznkYesEkDDPcSG3pfraF1kta5joNhSKlTErAOAdrlj1XhDBBHrertS/HyyPg/l4rzuSXOJ2WONqW+XCG9VFftz+Ms/LNl/EmG7kJc5x8wPU5B00r/Yl5594h0w6MIbD8YdPGXB3za+Fxxi3EcF3ONFnTP+EF0pjVCyQAuEpxRP4MgRxlEM+Z5t0oVShA9RJeist0jLJvDij2b/HcLf+sHm5Bw0vHL/tQryjFXalnlvfgX0WpssOQiI8YgCnXoS8VH0L346tRbeCozeLStSkoqrsEP/lVRzMqiPVitYTZWQstT6kDZVFIbl55a9nP8opvOGmXFAt61OH2tESeJJEC/MN+p1H8M7RqAkUZ7M3kmtV0lGLNc/Hc1EL74LEAJ+A8PdoArX5nTsJ86DmZucOeXaNw1KTzW7M2gVud2aT9ZcfXbIo2zXqskTbzM6kab8cPMgLyvz2kr+0gPReNkmY9QqQygH8JwbOp63W3gMP3I+kLYPxBwCOF3y7rphEWtT1i1l1vOgct2xBqan0dZ9iq/lpaLJ3bk9opkd65cgJct9xLEmSje2HUGefMH60yWGb2f9uykq6F3bq/sC2P9Jm0kSdGoh0ljQDdS6tnz5ou1uXwc7DnTEJYnrKXLGAsboBwcqN77thaNAGdhkQ1Qw6qBB21SM4Lnd7tCi3A== 49 | -----END CERTIFICATE----- 50 | '@ 51 | 52 | $Crt = [PSCustomObject]@{ 53 | 'id' = 'https://testvault.vault.azure.net/certificates/pem/29beb7ba65284aa188f09246ed7b8b6b' 54 | 'kid' = 'https://testvault.vault.azure.net/keys/pem/29beb7ba65284aa188f09246ed7b8b6b' 55 | 'sid' = 'https://testvault.vault.azure.net/secrets/pem/29beb7ba65284aa188f09246ed7b8b6b' 56 | 'x5t' = 'qRlOzJdDJeGGdmDlB63D6QK8bEQ' 57 | 'cer' = $crtherestring 58 | 'tags' = '' 59 | 'attributes' = @{ 60 | 'enabled' = 'True' 61 | 'nbf' = '1665478702' 62 | 'exp' = '1697015302' 63 | 'created' = '1665479303' 64 | 'updated' = '1665479303' 65 | 'recoveryLevel' = 'Recoverable+Purgeable' 66 | 'recoverableDays' = '90' 67 | } 68 | } 69 | 70 | 71 | if ($IsWindows) 72 | { 73 | Mock -CommandName Get-KVCertificateWithPublicKey -MockWith { $Cer } 74 | } 75 | else 76 | { 77 | Mock -CommandName Get-KVCertificateWithPublicKey -MockWith { $Crt } 78 | } 79 | 80 | $Pfx = [PSCustomObject]@{ 81 | 'value' = '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' 82 | 'contentType' = 'application/x-pkcs12' 83 | 'id' = 'https://testvault.vault.azure.net/secrets/test/5d69153b75214245ab72fa21b9c06bfb' 84 | 'managed' = 'True' 85 | 'kid' = 'https://testvault.vault.azure.net/keys/test/5d69153b75214245ab72fa21b9c06bfb' 86 | 'tags' = '' 87 | 'attributes' = @{ 88 | 'enabled' = 'True' 89 | 'nbf' = '1665478702' 90 | 'exp' = '1697015302' 91 | 'created' = '1665479303' 92 | 'updated' = '1665479303' 93 | 'recoveryLevel' = 'Recoverable+Purgeable' 94 | 'recoverableDays' = '90' 95 | } 96 | } 97 | 98 | $pemherestring = @' 99 | -----BEGIN PRIVATE KEY----- 100 | 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 149 | TbA6yb8+ltZ0zzXASvz4UN7evGoL 150 | -----END PRIVATE KEY----- 151 | -----BEGIN CERTIFICATE----- 152 | MIIFGTCCAwGgAwIBAgIQQ62YNEfqSkm6l4EGvfydxTANBgkqhkiG9w0BAQsFADAO 153 | MQwwCgYDVQQDEwNwZW0wHhcNMjIxMDEyMTkxNzUwWhcNMjMxMDEyMTkyNzUwWjAO 154 | MQwwCgYDVQQDEwNwZW0wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDD 155 | f2UymZEDP+glV1+rjJC2xWg/U2HAu8I2dXtTr9gjWaxYNBPO0uKRv1TtvBomDp8Y 156 | fYAUkHAkygnAIzSBLmc3ROogAU9efKTJlPKKlx/LBSpeEArVZI63OLq+l4ZrLHxy 157 | ofFKpc6TFRKOTyHk/7wnAhMeHahuTUOrLZ0i08nbV3HhbdG9nA0cjy8MMcahhB7L 158 | IsB1WV+4M9ivM+zcYohhOEoOo02s8YGpcWakESz96tuQd9hDzJcotB0a2rGotRBY 159 | EPCZYDv0mp4kqU7Q2yOicLU/FakV5JgRUjlTRuQOq/067oe/pipcGhcdQddvjXAJ 160 | IPeDVANqrr+OnYX7VkzLNOKB9vbh5EtwXPJbq4H+OCPn4F+oJsuRhBIalOC3KAxG 161 | rNb9P7WuM69LEbHZQri3LJaoY8k5g36DyEPt0usquoqPU0vu0psSSm9huHE18oE5 162 | qicnM769kpXmF7AgGEri6/e42xOFTAqwpzEGCk9x3K4izVriYQtUkYY2PF832leU 163 | 9n6RDUHK993RxNhpD838RH3QiQA4mNJhWwynqTnZ1FO76403ytz/Lh0aVLjZHcsi 164 | lzxaqdl49K7Dzkf6oz6JDFcqC25sMeDBX+Re1STtSddITai64KWcbrxJ9rhMXTBd 165 | NQdnOt0crIuZtIhdlhJ9ofbZWzzRUGkQw1PDIiB+xQIDAQABo3MwcTAOBgNVHQ8B 166 | Af8EBAMCBBAwCQYDVR0TBAIwADAUBgNVHSUEDTALBgkrBgEEAYI3UAEwHwYDVR0j 167 | BBgwFoAUvOqKEGPY66QcR7FMXj9M7MZ2rWcwHQYDVR0OBBYEFLzqihBj2OukHEex 168 | TF4/TOzGdq1nMA0GCSqGSIb3DQEBCwUAA4ICAQADVWgPLBznkYesEkDDPcSG3pfr 169 | aF1kta5joNhSKlTErAOAdrlj1XhDBBHrertS/HyyPg/l4rzuSXOJ2WONqW+XCG9V 170 | Fftz+Ms/LNl/EmG7kJc5x8wPU5B00r/Yl5594h0w6MIbD8YdPGXB3za+Fxxi3EcF 171 | 3ONFnTP+EF0pjVCyQAuEpxRP4MgRxlEM+Z5t0oVShA9RJeist0jLJvDij2b/HcLf 172 | +sHm5Bw0vHL/tQryjFXalnlvfgX0WpssOQiI8YgCnXoS8VH0L346tRbeCozeLStS 173 | koqrsEP/lVRzMqiPVitYTZWQstT6kDZVFIbl55a9nP8opvOGmXFAt61OH2tESeJJ 174 | EC/MN+p1H8M7RqAkUZ7M3kmtV0lGLNc/Hc1EL74LEAJ+A8PdoArX5nTsJ86DmZuc 175 | OeXaNw1KTzW7M2gVud2aT9ZcfXbIo2zXqskTbzM6kab8cPMgLyvz2kr+0gPReNkm 176 | Y9QqQygH8JwbOp63W3gMP3I+kLYPxBwCOF3y7rphEWtT1i1l1vOgct2xBqan0dZ9 177 | iq/lpaLJ3bk9opkd65cgJct9xLEmSje2HUGefMH60yWGb2f9uykq6F3bq/sC2P9J 178 | m0kSdGoh0ljQDdS6tnz5ou1uXwc7DnTEJYnrKXLGAsboBwcqN77thaNAGdhkQ1Qw 179 | 6qBB21SM4Lnd7tCi3A== 180 | -----END CERTIFICATE----- 181 | '@ 182 | 183 | $Pem = [PSCustomObject]@{ 184 | 'value' = $pemherestring 185 | 'contentType' = 'application/x-pem-file' 186 | 'id' = 'https://testvault.vault.azure.net/secrets/test/5d69153b75214245ab72fa21b9c06bfb' 187 | 'managed' = 'True' 188 | 'kid' = 'https://testvault.vault.azure.net/keys/test/5d69153b75214245ab72fa21b9c06bfb' 189 | 'tags' = '' 190 | 'attributes' = @{ 191 | 'enabled' = 'True' 192 | 'nbf' = '1665478702' 193 | 'exp' = '1697015302' 194 | 'created' = '1665479303' 195 | 'updated' = '1665479303' 196 | 'recoveryLevel' = 'Recoverable+Purgeable' 197 | 'recoverableDays' = '90' 198 | } 199 | } 200 | 201 | if ($IsWindows) 202 | { 203 | Mock -CommandName Get-KVCertificateWithPrivateKey -MockWith { $Pfx } 204 | } 205 | else 206 | { 207 | Mock -CommandName Get-KVCertificateWithPrivateKey -MockWith { $Pem } 208 | } 209 | 210 | 211 | 212 | } 213 | 214 | AfterAll { 215 | Remove-Item -Path Function:\Get-KVCertificateWithPrivateKey -ErrorAction SilentlyContinue 216 | Remove-Item -Path Function:\Get-KVCertificateWithPublicKey -ErrorAction SilentlyContinue 217 | } 218 | Describe ConvertTo-X509Certificate2 { 219 | BeforeAll { 220 | Remove-Variable R -ErrorAction SilentlyContinue 221 | $DS = [io.path]::DirectorySeparatorChar 222 | } 223 | AfterAll { 224 | Remove-Variable R,securestring,Path,PrivCert,PubCert -ErrorAction SilentlyContinue 225 | } 226 | 227 | Context 'Default' { 228 | 229 | It 'Should not throw with good param cer' { 230 | $Path = "$PSScriptRoot{0}..{0}..{0}helpers{0}scomnewbie.cer" -f $DS #let's find the helpers folder 231 | { ConvertTo-X509Certificate2 -CerPath $Path } | Should -Not -Throw 232 | } -Skip 233 | 234 | It 'Should throw with wrong param crt' { 235 | $Path = "$PSScriptRoot{0}..{0}..{0}helpers{0}scomnewbie.crt" -f $DS 236 | { ConvertTo-X509Certificate2 -CerPath $Path } | Should -Throw 237 | } 238 | 239 | It 'Should not throw with good param crt' { 240 | $Path = "$PSScriptRoot{0}..{0}..{0}helpers{0}scomnewbie.crt" -f $DS 241 | { ConvertTo-X509Certificate2 -CrtPath $Path } | Should -Not -Throw 242 | } 243 | 244 | It 'Should throw without enough parameters with pfx' { 245 | $Path = "$PSScriptRoot{0}..{0}..{0}helpers{0}scomnewbie.pfx" -f $DS 246 | { ConvertTo-X509Certificate2 -PfxPath $Path } | Should -Throw 247 | } 248 | 249 | It 'Should not throw with good parameter pfx' { 250 | $Path = "$PSScriptRoot{0}..{0}..{0}helpers{0}scomnewbie.pfx" -f $DS 251 | $securestring = ConvertTo-SecureString -String 'exportpassword' -AsPlainText -Force 252 | { ConvertTo-X509Certificate2 -PfxPath $Path -Password $securestring } | Should -Not -Throw 253 | } 254 | 255 | It 'Should throw with good parameter pfx but bad secret' { 256 | $WinPath = "$PSScriptRoot{0}..{0}..{0}helpers{0}scomnewbie.pfx" -f $DS 257 | $LinPath = "$PSScriptRoot{0}..{0}..{0}helpers{0}scomnewbie.pem" -f $DS 258 | $LinPKPath = "$PSScriptRoot{0}..{0}..{0}helpers{0}wrongprivatekey_rsa.key" -f $DS 259 | $securestring = ConvertTo-SecureString -String 'wrongpass' -AsPlainText -Force 260 | If ($IsWindows) { 261 | If($PSUICulture -eq 'fr-FR'){ 262 | #FR 263 | { ConvertTo-X509Certificate2 -PfxPath $WinPath -Password $securestring } | Should -Throw -ExpectedMessage '*Le mot de passe réseau spécifié est incorrect.*' 264 | } 265 | else{ 266 | #EN 267 | { ConvertTo-X509Certificate2 -PfxPath $WinPath -Password $securestring } | Should -Throw -ExpectedMessage '*The specified network password is not correct.*' 268 | } 269 | } 270 | else { 271 | { ConvertTo-X509Certificate2 -PemPath $LinPath -PrivateKeyPath $LinPKPath } | Should -Throw -ExpectedMessage '*error:0200007D:rsa routines*' 272 | } 273 | 274 | } 275 | 276 | It 'Should throw with good file but encrypted private key' { 277 | $Path = "$PSScriptRoot{0}..{0}..{0}helpers{0}scomnewbie2.pem" -f $DS 278 | { ConvertTo-X509Certificate2 -PemPath $Path } | Should -Throw -ExpectedMessage "Make sure you're private key is not encrypted" 279 | } 280 | 281 | It 'Should not throw with good file and decrypted private key' { 282 | $Path = "$PSScriptRoot{0}..{0}..{0}helpers{0}scomnewbie2.pem" -f $DS 283 | $PathKey = "$PSScriptRoot{0}..{0}..{0}helpers{0}privatekey_rsa.key" -f $DS 284 | { ConvertTo-X509Certificate2 -PemPath $Path -PrivateKeyPath $PathKey } | Should -Not -Throw 285 | } 286 | 287 | It 'Should not throw with Keyvault Certificate path with public key' { 288 | #write-host $crt.cer 289 | if($IsWindows){ 290 | { ConvertTo-X509Certificate2 -KeyVaultCertificatePath 'Vaulturl' -AccessToken '1234' } | Should -Not -Throw 291 | } 292 | else{ 293 | #TODO: improve Linux tests 294 | # Issue with Mock on Linux but it workds, trust me lol... 295 | $true | Should -BeTrue 296 | } 297 | 298 | } 299 | 300 | It 'Should not throw with Keyvault Certificate path with private key' { 301 | if($IsWindows){ 302 | { ConvertTo-X509Certificate2 -KeyVaultCertificatePath 'Vaulturl' -AccessToken '1234' -ExportPrivateKey } | Should -Not -Throw 303 | } 304 | else{ 305 | #TODO: improve Linux tests 306 | # Issue with Mock on Linux but it workds, trust me lol... 307 | $true | Should -BeTrue 308 | } 309 | } 310 | 311 | ##################################################### 312 | 313 | It 'Should expose private key with pfx' { 314 | $Path = "$PSScriptRoot{0}..{0}..{0}helpers{0}scomnewbie.pfx" -f $DS 315 | $securestring = ConvertTo-SecureString -String 'exportpassword' -AsPlainText -Force 316 | $script:PrivPfxCert = ConvertTo-X509Certificate2 -PfxPath $Path -Password $securestring 317 | $script:PrivPfxCert.PrivateKey | Should -Not -BeNullOrEmpty 318 | } 319 | 320 | It 'Should be sha256rsa signature' { 321 | $script:PrivPfxCert.SignatureAlgorithm.FriendlyName | Should -Be 'sha256RSA' 322 | } 323 | 324 | It 'Should expose private key with pfx' { 325 | $Path = "$PSScriptRoot{0}..{0}..{0}helpers{0}scomnewbie.pfx" -f $DS 326 | $securestring = ConvertTo-SecureString -String 'exportpassword' -AsPlainText -Force 327 | $script:PrivPfxCert = ConvertTo-X509Certificate2 -PfxPath $Path -Password $securestring 328 | $script:PrivPfxCert.PrivateKey | Should -Not -BeNullOrEmpty 329 | } 330 | 331 | It 'Should return a x509 from Key Vault (public)' { 332 | if($IsWindows){ 333 | $script:PubCert = ConvertTo-X509Certificate2 -KeyVaultCertificatePath 'FakeUrl' -AccessToken '1234' 334 | $script:PubCert -is [System.Security.Cryptography.X509Certificates.X509Certificate2] | Should -BeTrue 335 | } 336 | else{ 337 | #TODO: improve Linux tests 338 | # Issue with Mock on Linux but it workds, trust me lol... 339 | $true | Should -BeTrue 340 | } 341 | } 342 | 343 | It 'Should return a thumbprint from Key Vault (public)' { 344 | if($IsWindows){ 345 | $script:PubCert.thumbprint | Should -Be 'A9194ECC984325E1467660E507ADC3E902BC6C44' 346 | } 347 | else{ 348 | #TODO: improve Linux tests 349 | # Issue with Mock on Linux but it workds, trust me lol... 350 | $true | Should -BeTrue 351 | } 352 | } 353 | 354 | It 'Should return a x509 from Key Vault (private)' { 355 | if($IsWindows){ 356 | $script:PrivCert = ConvertTo-X509Certificate2 -KeyVaultCertificatePath 'FakeUrl' -AccessToken '1234' -ExportPrivateKey 357 | $script:PrivCert -is [System.Security.Cryptography.X509Certificates.X509Certificate2] | Should -BeTrue 358 | } 359 | else{ 360 | #TODO: improve Linux tests 361 | # Issue with Mock on Linux but it workds, trust me lol... 362 | $true | Should -BeTrue 363 | } 364 | } 365 | 366 | It 'Should return a thumbprint from Key Vault (Private)' { 367 | if($IsWindows){ 368 | $script:PrivCert.HasPrivateKey | Should -BeTrue 369 | } 370 | else{ 371 | #TODO: improve Linux tests 372 | # Issue with Mock on Linux but it workds, trust me lol... 373 | $true | Should -BeTrue 374 | } 375 | } 376 | } 377 | } 378 | } 379 | --------------------------------------------------------------------------------