Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions powershell/internal/Get-MtGitHubRepoFromGit.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
function Get-MtGitHubRepoFromGit {
<#
.SYNOPSIS
Detects the current GitHub repository from the local git remote (origin).

.DESCRIPTION
Used by Add-MtMaesterAppFederatedCredential and New-MtMaesterApp to remove
the need for the user to specify -GitHubOrganization and -GitHubRepository
when running the command from inside a git working tree whose `origin` remote
points at GitHub.

Supports both HTTPS and SSH remote URL formats:
https://github.com/owner/repo.git
https://github.com/owner/repo
git@github.com:owner/repo.git
Comment thread
SamErde marked this conversation as resolved.

Returns $null when:
* git is not installed / not on PATH
* the current directory is not inside a git working tree
* the origin remote is not a GitHub URL
* the URL cannot be parsed

.OUTPUTS
[pscustomobject] with Organization, Repository, RemoteUrl properties, or $null.
#>
[CmdletBinding()]
[OutputType([pscustomobject])]
param(
# Optional override for the git remote name. Defaults to 'origin'.
[string] $RemoteName = 'origin'
)

if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
Write-Verbose "git is not available on PATH; cannot auto-detect GitHub repo."
return $null
}

try {
$remoteUrl = (& git remote get-url $RemoteName 2>$null) | Select-Object -First 1
} catch {
Write-Verbose "Failed to read git remote '$RemoteName': $($_.Exception.Message)"
return $null
}

if ([string]::IsNullOrWhiteSpace($remoteUrl)) {
Write-Verbose "git remote '$RemoteName' is not configured."
return $null
}

# Match HTTPS, SSH, and scp-style GitHub URLs.
$pattern = '^(?:https?://[^/]*github\.com/|git@github\.com:|ssh://git@github\.com/)([^/]+)/([^/]+?)(?:\.git)?/?\s*$'
Comment thread
SamErde marked this conversation as resolved.
Outdated
Comment thread
SamErde marked this conversation as resolved.
Outdated
if ($remoteUrl -notmatch $pattern) {
Write-Verbose "Remote URL '$remoteUrl' is not a recognised GitHub URL."
return $null
}

return [pscustomobject]@{
Organization = $Matches[1]
Repository = $Matches[2]
RemoteUrl = $remoteUrl.Trim()
}
}
70 changes: 70 additions & 0 deletions powershell/internal/Set-MtGitHubActionsSecret.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
function Set-MtGitHubActionsSecret {
<#
.SYNOPSIS
Sets AZURE_CLIENT_ID and AZURE_TENANT_ID as GitHub Actions repository secrets via the GitHub CLI.

.DESCRIPTION
Used by Add-MtMaesterAppFederatedCredential when -SetGitHubSecrets is specified.
Returns $true when both secrets were set successfully, $false otherwise (caller
should fall back to printing manual setup instructions).

Requires the GitHub CLI (`gh`) to be installed and authenticated. Will validate
both before attempting any state-changing call.

.OUTPUTS
[bool] - $true on success, $false if gh is missing/unauthenticated or any
`gh secret set` call fails.
#>
[CmdletBinding()]
[OutputType([bool])]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'Colors are beautiful')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'User opted in via -SetGitHubSecrets switch on the calling cmdlet')]
param(
# Target repository in 'owner/repo' format.
[Parameter(Mandatory = $true)]
[string] $GitHubRepository,

# Application (Client) ID to store as AZURE_CLIENT_ID.
[Parameter(Mandatory = $true)]
[string] $ClientId,

# Entra Tenant ID to store as AZURE_TENANT_ID.
[Parameter(Mandatory = $true)]
[string] $TenantId
)

if (-not (Get-Command gh -ErrorAction SilentlyContinue)) {
Write-Warning "GitHub CLI ('gh') is not installed or not on PATH. Falling back to manual instructions."
Write-Host "Install gh from https://cli.github.com/ to enable -SetGitHubSecrets." -ForegroundColor DarkGray
return $false
}

# Validate gh auth - 'gh auth status' exits 0 when authenticated.
& gh auth status 2>&1 | Out-Null
if ($LASTEXITCODE -ne 0) {
Write-Warning "GitHub CLI is not authenticated. Run 'gh auth login' first. Falling back to manual instructions."
return $false
Comment thread
SamErde marked this conversation as resolved.
}

Write-Host "Setting GitHub Actions secrets on $GitHubRepository via gh CLI..." -ForegroundColor Yellow

$secrets = [ordered]@{
AZURE_CLIENT_ID = $ClientId
AZURE_TENANT_ID = $TenantId
Comment thread
SamErde marked this conversation as resolved.
}

foreach ($name in $secrets.Keys) {
$value = $secrets[$name]
$output = & gh secret set $name --repo $GitHubRepository --body $value 2>&1
if ($LASTEXITCODE -ne 0) {
Write-Warning "Failed to set $name on $GitHubRepository : $output"
return $false
}
Write-Host " ✓ $name set" -ForegroundColor Green
}

Write-Host ""
Write-Host "✅ AZURE_CLIENT_ID and AZURE_TENANT_ID configured on $GitHubRepository." -ForegroundColor Green
Write-Host ""
return $true
}
95 changes: 72 additions & 23 deletions powershell/public/core/Add-MtMaesterAppFederatedCredential.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,22 @@
The Application (Client) ID of the Maester application to add the federated credential to.

.PARAMETER GitHubRepository
The GitHub repository in the format 'owner/repo' (e.g., 'myorg/myrepo').
The GitHub repository name (without the organization). E.g. maester-tests.
If omitted (together with -GitHubOrganization) and the current working directory is
inside a git repository whose 'origin' remote points at GitHub, the repository is
auto-detected from `git remote get-url origin`.

.PARAMETER GitHubBranch
The GitHub branch that can use this credential. Defaults to 'main'.

.PARAMETER Name
The name for the federated credential. Defaults to 'maester-devops'.
The name for the federated credential. Defaults to 'maester-devops-<org>-<repo>'.

.PARAMETER SetGitHubSecrets
If specified, sets the AZURE_CLIENT_ID and AZURE_TENANT_ID secrets on the target
GitHub repository using the GitHub CLI (`gh`). Requires `gh` to be installed and
authenticated (`gh auth login`). When the secrets cannot be set automatically the
cmdlet falls back to printing the manual setup instructions.
Comment thread
SamErde marked this conversation as resolved.

.PARAMETER Force
Skip the confirmation prompt if a similar credential already exists.
Expand All @@ -37,6 +46,13 @@

Adds a federated credential for the develop branch with a custom name.

.EXAMPLE
Add-MtMaesterAppFederatedCredential -AppId "12345678-1234-1234-1234-123456789012" -SetGitHubSecrets

Auto-detects the GitHub organization and repository from the current git remote, adds
the federated credential, and pushes AZURE_CLIENT_ID / AZURE_TENANT_ID to the repo's
Actions secrets via the GitHub CLI.

.LINK
https://maester.dev/docs/commands/Add-MtMaesterAppFederatedCredential
#>
Expand All @@ -53,14 +69,15 @@
[Alias('ClientId')]
[string] $AppId,

# Your GitHub organization name or GitHub username. E.g. jasonf
[Parameter(Mandatory = $true, ParameterSetName = 'ById')]
[Parameter(Mandatory = $true, ParameterSetName = 'ByApplicationId')]
# Your GitHub organization name or GitHub username. E.g. jasonf.
# If omitted (together with -GitHubRepository) the value is auto-detected from
# the local git remote ('origin') when the current directory is a git repo.
[Parameter(Mandatory = $false)]
[string] $GitHubOrganization,

# Your GitHub repository name where the GitHub Actions workflow is located. E.g. maester-tests
[Parameter(Mandatory = $true, ParameterSetName = 'ById')]
[Parameter(Mandatory = $true, ParameterSetName = 'ByApplicationId')]
# Your GitHub repository name where the GitHub Actions workflow is located. E.g. maester-tests.
# Auto-detected from the local git remote ('origin') when omitted.
[Parameter(Mandatory = $false)]
[string] $GitHubRepository,

# The GitHub branch that can use this credential
Expand All @@ -69,13 +86,34 @@

# The name for the federated credential
[Parameter(Mandatory = $false)]
[string] $Name
[string] $Name,

# If set, also pushes AZURE_CLIENT_ID and AZURE_TENANT_ID to the GitHub repo's
# Actions secrets using the GitHub CLI (`gh`). Falls back to printing manual
# instructions if `gh` is not installed or not authenticated.
[Parameter(Mandatory = $false)]
[switch] $SetGitHubSecrets
)

if (-not (Test-MtAzContext)) {
return
}

# Auto-detect GitHub org/repo from the local git remote when caller didn't provide them.
if (-not $GitHubOrganization -or -not $GitHubRepository) {
$detected = Get-MtGitHubRepoFromGit
if ($detected) {
if (-not $GitHubOrganization) { $GitHubOrganization = $detected.Organization }
if (-not $GitHubRepository) { $GitHubRepository = $detected.Repository }
Write-Host "Auto-detected GitHub repository from git remote: $GitHubOrganization/$GitHubRepository" -ForegroundColor Cyan
}
}

if (-not $GitHubOrganization -or -not $GitHubRepository) {
Write-Error "GitHubOrganization and GitHubRepository are required. They can be auto-detected when the current directory is a git working tree whose 'origin' remote points at GitHub."
return
}

try {
if ($Id) {
$params = @{ Id = $Id }
Expand Down Expand Up @@ -146,23 +184,34 @@
}

$githubSecretsUrl = "https://github.com/$GitHubOrganization/$GitHubRepository/settings/secrets/actions"
$tenantId = (Get-AzContext).Tenant.Id

Write-Host ""
Write-Host "🎉 Federated credential created successfully!" -ForegroundColor Green
Write-Host ""
Write-Host "GitHub Actions Configuration:" -ForegroundColor Yellow
Write-Host "Add these secrets to your GitHub repository ($GitHubOrganization/$GitHubRepository):" -ForegroundColor White
Write-Host ""
Write-Host "1. Browse to $githubSecretsUrl" -ForegroundColor Cyan
Write-Host "2. Click on 'New repository secret'" -ForegroundColor Cyan
Write-Host "3. Create the following secrets:" -ForegroundColor Cyan
Write-Host ""
Write-Host " Name: AZURE_CLIENT_ID" -ForegroundColor Cyan
Write-Host " Value: $($app.AppId)" -ForegroundColor Cyan
Write-Host " Name: AZURE_TENANT_ID" -ForegroundColor Cyan
Write-Host " Value: $((Get-AzContext).Tenant.Id)" -ForegroundColor Cyan
Write-Host ""
Write-Host "See https://maester.dev/docs/monitoring/github#add-entra-tenant-info-to-github-repos for details." -ForegroundColor Yellow
Write-Host ""

$secretsConfigured = $false
if ($SetGitHubSecrets) {
$secretsConfigured = Set-MtGitHubActionsSecret -GitHubRepository "$GitHubOrganization/$GitHubRepository" -ClientId $app.AppId -TenantId $tenantId
}
Comment thread
SamErde marked this conversation as resolved.

if (-not $secretsConfigured) {
Write-Host "GitHub Actions Configuration:" -ForegroundColor Yellow
Write-Host "Add these secrets to your GitHub repository ($GitHubOrganization/$GitHubRepository):" -ForegroundColor White
Write-Host ""
Write-Host "1. Browse to $githubSecretsUrl" -ForegroundColor Cyan
Write-Host "2. Click on 'New repository secret'" -ForegroundColor Cyan
Write-Host "3. Create the following secrets:" -ForegroundColor Cyan
Write-Host ""
Write-Host " Name: AZURE_CLIENT_ID" -ForegroundColor Cyan
Write-Host " Value: $($app.AppId)" -ForegroundColor Cyan
Write-Host " Name: AZURE_TENANT_ID" -ForegroundColor Cyan
Write-Host " Value: $tenantId" -ForegroundColor Cyan
Write-Host ""
Write-Host "Tip: re-run with -SetGitHubSecrets to push these via the GitHub CLI automatically." -ForegroundColor DarkGray
Write-Host "See https://maester.dev/docs/monitoring/github#add-entra-tenant-info-to-github-repos for details." -ForegroundColor Yellow
Write-Host ""
}

return $createdCredential

Expand Down
64 changes: 59 additions & 5 deletions powershell/public/core/New-MtMaesterApp.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,25 @@
.PARAMETER Scopes
Additional custom permission scopes to include beyond the default Maester scopes.

.PARAMETER GitHubOrganization
Your GitHub organization name or GitHub username (e.g. 'jasonf'). When supplied
together with -GitHubRepository the cmdlet will also create a federated identity
credential for GitHub Actions OIDC.

.PARAMETER GitHubRepository
Your GitHub repository name where the workflow lives (e.g. 'maester-tests').

.PARAMETER GitHubActions
Enable end-to-end GitHub Actions setup. Creates a federated identity credential
after granting permissions, and auto-detects the GitHub organization/repository
from the local git remote ('origin') when -GitHubOrganization/-GitHubRepository
are not explicitly supplied.

.PARAMETER SetGitHubSecrets
Together with -GitHubActions, also pushes AZURE_CLIENT_ID and AZURE_TENANT_ID
to the target repository's Actions secrets via the GitHub CLI ('gh'). Falls back
to printing manual instructions when 'gh' is unavailable or not authenticated.
Comment thread
SamErde marked this conversation as resolved.
Outdated

.EXAMPLE
New-MtMaesterApp

Expand All @@ -41,6 +60,13 @@

Creates a new Maester app with privileged scopes and additional custom scopes.

.EXAMPLE
New-MtMaesterApp -GitHubActions -SetGitHubSecrets

Full zero-config GitHub Actions setup. Auto-detects the target repository from
the current git remote, creates the app, grants permissions, adds the federated
credential, and pushes the AZURE_CLIENT_ID / AZURE_TENANT_ID secrets via gh CLI.

.LINK
https://maester.dev/docs/commands/New-MtMaesterApp
#>
Expand Down Expand Up @@ -68,7 +94,16 @@
[string] $GitHubOrganization,

# Your GitHub repository name where the GitHub Actions workflow is located. E.g. maester-tests
[string] $GitHubRepository
[string] $GitHubRepository,

# Enable end-to-end GitHub Actions setup (creates a federated identity credential).
# Auto-detects -GitHubOrganization/-GitHubRepository from the local git remote when
# they are not explicitly provided.
[switch] $GitHubActions,

# Together with -GitHubActions, push AZURE_CLIENT_ID/AZURE_TENANT_ID to the repo's
# Actions secrets via the GitHub CLI ('gh').
[switch] $SetGitHubSecrets
)

# We use the Azure module to create the app registration since it has pre-consented permissions to create apps
Expand All @@ -79,9 +114,22 @@
return
}

if ($GitHubOrganization -or $GitHubRepository) {
# Treat any GitHub-flow parameter as opting into the GitHub Actions path.
$useGitHubFlow = $GitHubActions -or $SetGitHubSecrets -or $GitHubOrganization -or $GitHubRepository

if ($useGitHubFlow) {
# Auto-detect from local git remote when org/repo not explicitly provided.
if (-not $GitHubOrganization -or -not $GitHubRepository) {
Write-Error "Both GitHubOrganization and GitHubRepository must be specified to add a federated credential."
$detected = Get-MtGitHubRepoFromGit
if ($detected) {
if (-not $GitHubOrganization) { $GitHubOrganization = $detected.Organization }
if (-not $GitHubRepository) { $GitHubRepository = $detected.Repository }
Write-Host "Auto-detected GitHub repository from git remote: $GitHubOrganization/$GitHubRepository" -ForegroundColor Cyan
}
}

if (-not $GitHubOrganization -or -not $GitHubRepository) {
Write-Error "Both GitHubOrganization and GitHubRepository must be specified to add a federated credential. They can be auto-detected when the current directory is a git working tree whose 'origin' remote points at GitHub."
return
}
}
Expand Down Expand Up @@ -171,8 +219,14 @@
Write-Host " Ensure the account running New-MtMaesterApp has Privileged Role Administrator or Global Administrator rights." -ForegroundColor Yellow
}

if ($GitHubOrganization) {
Add-MtMaesterAppFederatedCredential -AppId $app.appId -GitHubOrganization $GitHubOrganization -GitHubRepository $GitHubRepository
if ($useGitHubFlow) {
$ficParams = @{
AppId = $app.appId
GitHubOrganization = $GitHubOrganization
GitHubRepository = $GitHubRepository
}
if ($SetGitHubSecrets) { $ficParams['SetGitHubSecrets'] = $true }
Add-MtMaesterAppFederatedCredential @ficParams
} else {
Write-Output $result
}
Expand Down
Loading
Loading