Skip to content

Commit a3648e4

Browse files
Create YAML Kerberos Test Pipeline (#4252)
Co-authored-by: Priyanka Tiwari <prtiwar@microsoft.com>
1 parent 8266a5a commit a3648e4

6 files changed

Lines changed: 600 additions & 0 deletions
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
#################################################################################
2+
# Licensed to the .NET Foundation under one or more agreements. #
3+
# The .NET Foundation licenses this file to you under the MIT license. #
4+
# See the LICENSE file in the project root for more information. #
5+
#################################################################################
6+
7+
# Shared build-and-test steps used by both the Windows and Linux Kerberos jobs.
8+
#
9+
# Parameters:
10+
# buildTarget — The build.proj target that builds SqlClient for the current
11+
# OS (e.g. BuildSqlClientWindows or BuildSqlClientUnix).
12+
# testFramework — The TFM to test against (e.g. net9.0, net462).
13+
# testRunTitle — Title for the published test results (displayed in the ADO
14+
# Tests tab).
15+
# artifactName — Name of the published pipeline artifact that carries the
16+
# test results and coverage files.
17+
18+
parameters:
19+
20+
# build.proj target to build SqlClient for the current OS.
21+
- name: buildTarget
22+
type: string
23+
24+
# TFM to pass to the test targets (-p:TestFramework).
25+
- name: testFramework
26+
type: string
27+
28+
# Title shown in the ADO Tests tab for this run.
29+
- name: testRunTitle
30+
type: string
31+
32+
# Pipeline artifact name for test results and coverage.
33+
- name: artifactName
34+
type: string
35+
36+
steps:
37+
38+
# ---------------------------------------------------------------------------
39+
# Build
40+
# ---------------------------------------------------------------------------
41+
42+
# Build the given target.
43+
#
44+
# The test stages build as part of their targets, but this separate step isolates build failures
45+
# so we can fail fast before running tests. Retries are enabled intentionally (1 attempt for
46+
# build, 2 attempts for test steps) to reduce transient infrastructure-related failures.
47+
#
48+
- task: DotNetCoreCLI@2
49+
displayName: Build SqlClient
50+
retryCountOnTaskFailure: 1
51+
inputs:
52+
command: build
53+
projects: build.proj
54+
arguments: >-
55+
-t:${{ parameters.buildTarget }}
56+
-p:Configuration=Release
57+
58+
# ---------------------------------------------------------------------------
59+
# Run tests in separate steps to permit focused retries.
60+
# ---------------------------------------------------------------------------
61+
62+
# Run the Unit Test suite.
63+
- task: DotNetCoreCLI@2
64+
displayName: Run Unit Tests (${{ parameters.testFramework }})
65+
retryCountOnTaskFailure: 2
66+
inputs:
67+
command: build
68+
projects: build.proj
69+
arguments: >-
70+
-t:TestSqlClientUnit
71+
-p:TestFramework=${{ parameters.testFramework }}
72+
-p:Configuration=Release
73+
74+
# Run the Functional Test suite.
75+
- task: DotNetCoreCLI@2
76+
displayName: Run Functional Tests (${{ parameters.testFramework }})
77+
retryCountOnTaskFailure: 2
78+
inputs:
79+
command: build
80+
projects: build.proj
81+
arguments: >-
82+
-t:TestSqlClientFunctional
83+
-p:TestFramework=${{ parameters.testFramework }}
84+
-p:Configuration=Release
85+
86+
# Run the Manual Test suite.
87+
- task: DotNetCoreCLI@2
88+
displayName: Run Manual Tests (${{ parameters.testFramework }})
89+
retryCountOnTaskFailure: 2
90+
inputs:
91+
command: build
92+
projects: build.proj
93+
arguments: >-
94+
-t:TestSqlClientManual
95+
-p:TestFramework=${{ parameters.testFramework }}
96+
-p:Configuration=Release
97+
98+
# ---------------------------------------------------------------------------
99+
# Publish results & coverage
100+
# ---------------------------------------------------------------------------
101+
102+
# Publish the TRX test results to the pipeline run.
103+
- task: PublishTestResults@2
104+
displayName: Publish Test Results
105+
condition: succeededOrFailed()
106+
inputs:
107+
testResultsFormat: VSTest
108+
# build.proj defines TestResultsFolderPath which defaults to
109+
# $(Build.SourcesDirectory)/test_results, so we look there for the results and coverage files.
110+
testResultsFiles: $(Build.SourcesDirectory)/test_results/**/*.trx
111+
mergeTestResults: true
112+
testRunTitle: ${{ parameters.testRunTitle }}
113+
buildConfiguration: Release
114+
115+
# Azure Pipelines task conditions do not support path existence checks directly,
116+
# so compute this once and gate later steps on the variable.
117+
- pwsh: |
118+
$resultsDir = "$(Build.SourcesDirectory)/test_results"
119+
if (Test-Path -LiteralPath $resultsDir) {
120+
Write-Host "##vso[task.setvariable variable=HasTestResultsDir]true"
121+
}
122+
else {
123+
Write-Host "##vso[task.setvariable variable=HasTestResultsDir]false"
124+
}
125+
displayName: Detect test_results directory
126+
condition: succeededOrFailed()
127+
128+
# Give our coverage files a unique name to make it clear where they originated when we download
129+
# the artifacts from all jobs in the merge stage.
130+
- pwsh: |
131+
cd $(Build.SourcesDirectory)/test_results
132+
Get-ChildItem -Filter "*.coverage" -Recurse |
133+
Rename-Item -NewName { "${{ parameters.testFramework }}" + $_.Name }
134+
displayName: Rename coverage files
135+
condition: and(succeededOrFailed(), eq(variables['HasTestResultsDir'], 'true'))
136+
137+
# Publish TRX test results and coverage files as pipeline artifacts. The merge stage needs the
138+
# coverage files from all of the jobs.
139+
- task: PublishPipelineArtifact@1
140+
displayName: Publish Test Artifacts
141+
condition: and(succeededOrFailed(), eq(variables['HasTestResultsDir'], 'true'))
142+
inputs:
143+
targetPath: $(Build.SourcesDirectory)/test_results
144+
artifact: ${{ parameters.artifactName }}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#################################################################################
2+
# Licensed to the .NET Foundation under one or more agreements. #
3+
# The .NET Foundation licenses this file to you under the MIT license. #
4+
# See the LICENSE file in the project root for more information. #
5+
#################################################################################
6+
7+
# This template leaves the Active Directory domain and destroys Kerberos
8+
# credentials. It should be referenced at the end of any job that called
9+
# linux-init-step.yml.
10+
#
11+
# All steps use condition: always() so that cleanup runs even when previous
12+
# steps fail.
13+
14+
parameters:
15+
16+
# The Active Directory domain to leave (e.g. mydomain.contoso.com).
17+
- name: kerberosDomain
18+
type: string
19+
20+
# The domain user account used during the join.
21+
- name: kerberosDomainUser
22+
type: string
23+
24+
# The password for the domain user account.
25+
- name: kerberosDomainPassword
26+
type: string
27+
28+
steps:
29+
30+
- bash: |
31+
set -uo pipefail
32+
33+
DOMAIN="${{ parameters.kerberosDomain }}"
34+
DOMAIN_USER="${{ parameters.kerberosDomainUser }}"
35+
DOMAIN_PASSWORD="${{ parameters.kerberosDomainPassword }}"
36+
DOMAIN_UPPER=$(echo "$DOMAIN" | tr '[:lower:]' '[:upper:]')
37+
38+
# Leave the domain
39+
echo "$DOMAIN_PASSWORD" | sudo realm leave "$DOMAIN_UPPER" --verbose \
40+
-U "$DOMAIN_USER@$DOMAIN_UPPER" || true
41+
42+
# Destroy the TGT and credential cache
43+
kdestroy || true
44+
displayName: Clean up Kerberos (domain leave + kdestroy)
45+
condition: always()
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
#################################################################################
2+
# Licensed to the .NET Foundation under one or more agreements. #
3+
# The .NET Foundation licenses this file to you under the MIT license. #
4+
# See the LICENSE file in the project root for more information. #
5+
#################################################################################
6+
7+
# This template joins a Linux agent to an Active Directory domain using Kerberos
8+
# and acquires a TGT (Ticket-Granting Ticket) for the specified domain user.
9+
#
10+
# Prerequisites:
11+
# - The agent must be running on Ubuntu/Debian (uses apt-get).
12+
# - The domain controller must be reachable from the agent network.
13+
#
14+
# After this step completes successfully, the agent will have:
15+
# - Kerberos packages installed (krb5-user, realmd, sssd, adcli, etc.)
16+
# - Hostname set to FQDN within the domain
17+
# - NTP synchronized with the domain controller
18+
# - Machine joined to the AD domain
19+
# - A valid Kerberos TGT for the specified user
20+
21+
parameters:
22+
23+
# The Active Directory domain to join (e.g. mydomain.contoso.com).
24+
- name: kerberosDomain
25+
type: string
26+
27+
# The Organizational Unit in which to place the computer account.
28+
- name: kerberosDomainOU
29+
type: string
30+
31+
# The domain user account to authenticate with (sAMAccountName, without @realm).
32+
- name: kerberosDomainUser
33+
type: string
34+
35+
# The password for the domain user account.
36+
- name: kerberosDomainPassword
37+
type: string
38+
39+
steps:
40+
41+
- bash: |
42+
set -euo pipefail
43+
44+
DOMAIN="${{ parameters.kerberosDomain }}"
45+
DOMAIN_OU="${{ parameters.kerberosDomainOU }}"
46+
DOMAIN_USER="${{ parameters.kerberosDomainUser }}"
47+
DOMAIN_PASSWORD="${{ parameters.kerberosDomainPassword }}"
48+
DOMAIN_UPPER=$(echo "$DOMAIN" | tr '[:lower:]' '[:upper:]')
49+
50+
echo "Domain: $DOMAIN"
51+
echo "Realm: $DOMAIN_UPPER"
52+
echo "User: $DOMAIN_USER"
53+
echo "OU: $DOMAIN_OU"
54+
55+
if [ -z "$DOMAIN_PASSWORD" ]; then
56+
echo "##vso[task.logissue type=error]KerberosDomainPassword is empty"
57+
exit 1
58+
fi
59+
60+
# -----------------------------------------------------------------------
61+
# Install Kerberos and AD integration packages
62+
# -----------------------------------------------------------------------
63+
echo 'debconf debconf/frontend select Noninteractive' | sudo debconf-set-selections
64+
65+
sudo apt-get -y update
66+
sudo apt-get install -y dialog apt-utils
67+
sudo apt-get install -y \
68+
krb5-user samba sssd sssd-tools libnss-sss libpam-sss \
69+
ntp ntpdate realmd adcli
70+
71+
# -----------------------------------------------------------------------
72+
# Set the hostname to FQDN within the domain
73+
# -----------------------------------------------------------------------
74+
CURRENT_HOSTNAME="$(hostname)"
75+
if [ "$CURRENT_HOSTNAME" = "$DOMAIN" ] || [[ "$CURRENT_HOSTNAME" == *".$DOMAIN" ]]; then
76+
echo "Hostname already uses domain suffix '.$DOMAIN': $CURRENT_HOSTNAME"
77+
else
78+
sudo hostnamectl set-hostname "$CURRENT_HOSTNAME.$DOMAIN"
79+
fi
80+
81+
# -----------------------------------------------------------------------
82+
# Synchronize time with the domain controller (required for Kerberos)
83+
# -----------------------------------------------------------------------
84+
if ! sudo grep -Fqx "server $DOMAIN" /etc/ntp.conf; then
85+
echo "server $DOMAIN" | sudo tee -a /etc/ntp.conf
86+
fi
87+
sudo systemctl stop ntp
88+
sudo ntpdate "$DOMAIN"
89+
sudo systemctl start ntp
90+
91+
# -----------------------------------------------------------------------
92+
# Configure Kerberos realm
93+
# -----------------------------------------------------------------------
94+
echo "[libdefaults]
95+
default_realm = $DOMAIN_UPPER
96+
rdns = false" | sudo tee /etc/krb5.conf
97+
98+
# -----------------------------------------------------------------------
99+
# Discover and join the domain
100+
# -----------------------------------------------------------------------
101+
sudo realm discover "$DOMAIN_UPPER"
102+
103+
echo "$DOMAIN_PASSWORD" | sudo realm join --verbose "$DOMAIN_UPPER" \
104+
-U "$DOMAIN_USER@$DOMAIN_UPPER" \
105+
--computer-ou "OU=$DOMAIN_OU"
106+
107+
realm list
108+
109+
# -----------------------------------------------------------------------
110+
# Acquire a Kerberos TGT
111+
# -----------------------------------------------------------------------
112+
echo "$DOMAIN_PASSWORD" | kinit "$DOMAIN_USER@$DOMAIN_UPPER"
113+
114+
klist
115+
sudo ip addr
116+
sudo ip route
117+
displayName: Initialize Kerberos (domain join + kinit)

0 commit comments

Comments
 (0)