Konfiguration und Verwaltung von Self-Hosted Agents

Im Online-Artikel „Managed DevOps Pools“ haben wir die Managed DevOps Pools unter die Lupe genommen. Diese sind den Kunden von Azure DevOps Services vorbehalten, während für On-Premises-Installationen von Azure DevOps bislang nur die Benutzung von selbst gehosteten Agents bleibt. Grund genug also, uns deren Konfiguration und Betrieb etwas genauer anzusehen.
Der eigentliche Agent bleibt derselbe. Microsoft stellt nur einen einzigen Pipeline Agent zur Verfügung. Bei den Managed Services ist also nur das Drumherum, also das Deployment und die Konfiguration sowie die VM-Umgebung, von Microsoft verwaltet. Der in der VM installierte Agent ist derselbe, der lokal als sogenannter Private Agent installiert wird.
Anbindung an Azure DevOps und Berechtigungen
Die Verbindung von einer ausführenden Pipeline zu einem Agent erfolgt über den Agent Pool, welcher einem Job einer Pipeline zugeteilt wird. Ein Job ist eine auf einem Agent ausgeführte Sequenz von Tasks und kann nicht weiter unterteilt werden. Eine Pipeline kann mehrere Jobs beinhalten, und entsprechend kann jeder Job einen Agent aus einem Pool beziehen. Sind mehrere Agents in einem Pool registriert, wird der nächste freie Agent der Job-Ausführung zugeteilt, sofern die geforderten Capabilities vorhanden sind. Jeder Agent trägt bei der Registrierung respektive dem Start-up im Pool sogenannte Capabilities ein.
Das sind einerseits Daten aus der Agent-Umgebung, wie zum Beispiel installierte SDKs und Dev-Tools. Zudem können auch eigene Capabilities für einen Agent registriert werden. Aus technischer Sicht sind es Key-Value-Pairs, welche entsprechend abgefragt werden können. Bei der Zuteilung von einem Agent Pool zu einem Pipeline-Job können mittels Demands die möglichen Agents für die Ausführung gefiltert werden.
Ist nun ein großer Agent Pool mit Demands-Filter einem kleineren vorzuziehen? Diese Frage lässt sich nicht ganz einfach beantworten, oder wie es im Berater-Jargon heißt: „Es kommt drauf an“. Pools werden auf Organization-Ebene verwaltet und dann einem oder mehreren Projekten zugeteilt. Grundsätzlich erziele ich mit einem großen Pool über mehrere Projekte eine bessere Verfügbarkeit und Auslastung der zur Verfügung stehenden Infrastruktur.
Bei kleinen Pools stauen sich oft Pipeline-Runs in einem Pool auf, während andere Pools nichts zu tun haben. Somit ist ein größerer Pool von Vorteil. Wie wir aber im Printartikel „Sicher ist sicher“ der dotnetpro 6-7/2025 ab Seite 54 gelernt haben, müssen verschiedene Zwecke und Ziel-Environments der Agents auch zwingend segmentiert werden.
Grundsätzlich sollen die Pools immer zwischen Build- und den verschiedenen Deployment-Environments unterschieden werden. Das Filtern von verschiedenen Agent-Typen im selben Pool kommt in der Praxis eher selten vor. Tool-Unterschiede werden oft über Container-Jobs gelöst. Hierbei werden die Tools mittels eines Docker-Containers zur Verfügung gestellt und die Agent-VM hat außer dem Agent und der Docker Runtime nichts weiter installiert. Somit erübrigt sich oft das Filtern mittels Demands, da die spezifischen Anforderungen an die Abhängigkeiten über das Container-Image erfolgt.
In Bild 1 ist eine mögliche Pool-Konfiguration veranschaulicht.

Agent Pools (Bild 1)
AutorDie Pools werden auf der Organization hinterlegt und können grundsätzlich von mehreren Projekten verwendet werden. Die Projekte verlinken also den projektspezifischen Pool. Die installierten Agents sind immer genau einem Pool zugeteilt (die Ausnahme hierbei ist ein Environment-Agent, der einer einzigen Environment-Maschine zugeteilt ist). Dem Pipeline-Job, welcher einen Pool referenziert, stehen also nur diejenigen Agent-Instanzen zur Verfügung, welche dem Pool zugeteilt sind. Ein größerer Pool schafft somit eine bessere Verteilung, birgt aber auch Sicherheitsrisiken.
Die Identity, unter welcher der Agent während des Pipeline-Runs läuft, kann entweder Zugriff auf die ganze Organization bekommen oder auf ein spezifisches Projekt limitiert werden. Diese Einschränkung betrifft jedoch nur die API-Calls. Wird ein Agent einem Pool zugeteilt, welcher von mehreren Projekten verwendet wird, und sind die Berechtigungen auf das Projekt eingeschränkt, hat der Agent-Prozess immer noch Zugriff auf lokale Ressourcen auf der Agent-VM, welche von einem anderen Projekt stammen können. Je nach Azure DevOps Organization und Projektkonfiguration kann diese seitliche Bewegung (englisch: lateral movement) eine Sicherheitslücke darstellen, und dann wäre eine komplette Isolierung der Pools pro Projekt empfohlen.
Die bislang besprochene Definition von Pools ist eine rein logische Definition. Gemäß Security-Best-Practices sind die Agent-Instanzen auch physikalisch voneinander zu trennen. Oft wird dies mittels vNETs realisiert. Generell sollte ein Build-Agent keinerlei Zugriff auf die Deployment-Ziele haben, und auch das Test-System muss zwingend vom produktiven System getrennt werden. Hier wäre ein „lateral movement“ bei einer Attacke fatal. Bild 2 zeigt eine solches Setup schematisch auf.

Physikalische Segmentierung von Pools und Agents (Bild 2)
AutorDie vNETs für Test- und Produktiv-Environment sind die eigentlichen vNETs der Zielsysteme. Somit benötigen die Agents keinen Zugriff von außen, und die Firewalls können somit sehr restriktiv konfiguriert werden. Das vNET für das Testsystem beinhaltet im Beispiel zwei Agent-Pools. Ein mögliches Szenario hierfür wäre, dass wir zwischen Deployment-Agents, also der reinen Auslieferung der Artefakte, und Test-Automatisierungs-Agents, welche interaktive UI-Tests ausführen, unterscheiden.
Agent-Pool-Berechtigungen
Werfen wir nun einen detaillierteren Blick auf die Berechtigungen und Einstellungen für einen Pool. Ist ein Pool definiert, sollten wir definieren, wer diesen Pool Anwenden und wer Agents dem Pool hinzufügen kann. Bild 3 zeigt diese Einstellungen.

Pool-Berechtigungen (Bild 3)
AutorGrundsätzlich können wir für die Verwaltung der Agents und des Pools aus drei Rollen auswählen:
- Reader: Diese Rolle wird grundsätzlich für das Monitoring der Build- und Deployment-Jobs verwendet und beinhaltet reine Lese-Rechte.
- User: Diese Rolle gestattet die Benutzung des Pools in einer Pipeline, also das Zuteilen eines Pools zu einem Job sowie dem Berechtigen der Pipeline im Pool.
- Administrator: Diese Rolle lässt das Verwalten des Pools inklusive Rollenzuweisungen zu.
Da die Pools eigentlich auf Stufe der Organization verwaltet werden, muss für das Hinzufügen eines Agents die Administrator-Rolle auf dem Pool auf Organization-Ebene zugeteilt werden.
In Bild 3 zudem ebenfalls die „Pipeline permissions“ ersichtlich.
Wird in einer YAML-Pipeline ein Pool zu einem Job hinzugefügt, muss die entsprechende Pipeline erst berechtigt werden. Beim ersten Pipeline-Run wird eine Freigabeaufforderung angezeigt, bei der die entsprechende Berechtigung erteilt werden muss. Dies kann aber auch proaktiv durchgeführt werden, indem die Pipelines in dieser View vorab berechtigt werden. Es sollte keinesfalls ein Blanko-Check ausgestellt werden, bei dem jegliche bestehende und zukünftige Pipelines den Pool benutzen dürfen. Die Freigabe sollte immer explizit erfolgen.
Sowohl auf Projekt- und Organization-Ebene können weitere sicherheitsrelevante Einstellungen für die Pools konfiguriert werden. Bild 4, 5 und 6 zeigen die aktuellen Möglichkeiten auf.

Organization-Einstellungen für den Pool (Bild 4)
Autor
Task Restrictions auf Organization-Ebene (Bild 5)
Autor
Projektspezifische Einstellungen für den Pool (Bild 6)
AutorWichtig ist, die API-Zugriffe der Pipeline möglichst klein zu halten. Hier kann entweder auf das Projekt der Pipeline eingeschränkt werden, oder aber man erteilt den Zugriff auf alle Ressourcen der Organization. Dies hängt stark von der Gesamtkonfiguration der Azure-DevOps-Installation ab. Generell gilt aber, so wenig Berechtigungen wie möglich zu erteilen.
Die Einstellungsmöglichkeiten gehen aber noch viel weiter. So kann der Zugriff auf Git-Repositories eingeschränkt werden, damit nur die von der Pipeline referenzierten Repositories für den Pipeline-Run freigeschaltet werden. Hinzu kommen Einschränkungen zum Setzen beziehungsweise Überschreiben von Pipeline-Variablen. Auch Skriptargumente können überprüft werden.
Diese beiden Optionen sind hilfreich, um Skript-Injections in Pipelines aktiv zu verhindern. Zusätzlich kann auch die Benutzung von Built-in Tasks oder Marketplace-Tasks untersagt werden. Auch hier gilt, so restriktiv wie möglich zu sein und eine Lockerung der Einstellungen erst einer Prüfung zu unterziehen, wieso dies wirklich notwendig ist.
Agent-Installation und Registrierung
Sobald der oder die Agent Pools eingerichtet sind, geht es darum, diese mit Agents auszustatten. Für das Hinzufügen eines Agents benötigt der ausführende Benutzer Administratorberechtigungen auf dem Pool auf Organization-Ebene. Wichtig zu erwähnen ist hierbei, dass der Benutzer, welcher den Agent dem Pool hinzufügt, nichts mit der Identity zur Laufzeit der Pipelineausführung zu tun hat. Dies sind zwei komplett unterschiedliche Identities und werden separat berechtigt. Grundsätzlich ist es zu empfehlen, den Download und die Konfiguration des Agents, die auch die Registrierung im Pool beinhaltet, über Skripte und/oder Infrastructure as Code zu automatisieren.
Die Agent-ZIP-Datei enthält neben den Binaries auch Skriptdateien für das jeweilige Betriebssystem. Somit ist also eine config.cmd für Windows und eine config.sh für Linux und Mac enthalten. Mit dieser Skriptdatei kann der Agent konfiguriert werden. Dies funktioniert über das Flag --unattended auch ohne Benutzerinteraktion. Insofern kann in der Umgebung, also im VM-Template oder bei Containern im Dockerfile, der entsprechende Skriptaufruf beim Start hinterlegt werden, und der Agent installiert sich entsprechend beim Erstellen der VM beziehungsweise des Containers.
Die erste wichtige Entscheidung ist, unter welchem Benutzer der Agent dem Pool hinzugefügt werden soll. Vor allem beim Azure DevOps Server verläuft der einfachste Weg immer noch über ein Personal Access Token (PAT). PATs sind ein wenig in Verruf geraten, auch wenn beim Token der Scope feingranular eingeschränkt werden kann.
Dennoch ist das Token an einen Benutzer gebunden und wird in einem IaC-Umfeld für eine automatisierte und unbeaufsichtigte Installation verwendet. Im Fall von Azure DevOps Services sieht es schon ein wenig besser aus, und wir können nebst Service Principals auch den Device Code Flow für eine semi-automatisierte Variante nutzen. Tabelle 1 zeigt eine Übersicht über die möglichen Authentisierungsoptionen zur Agent-Registrierung.
Tabelle 1: Authentisierungsmöglichkeiten zur Agent Registrierung
Registrierungsmethode des Agents | Azure DevOps Services | Azure DevOps Server & TFS |
Personal Access Token (PAT) | Unterstützt | Unterstützt, wenn der Server mit HTTPS konfiguriert ist |
Service Principal (SP) | Unterstützt | Derzeit nicht unterstützt |
Device-Code-Flow (Microsoft Entra ID) | Unterstützt | Derzeit nicht unterstützt |
Integrated | Nicht unterstützt | Nur Windows-Agenten |
Negotiate | Nicht unterstützt | Nur Windows-Agenten |
Alternate (ALT) | Nicht unterstützt | Unterstützt, wenn der Server mit HTTPS konfiguriert ist |
Ein Agent läuft entweder als Service oder als interaktiver Prozess. Grundsätzlich wird der interaktive Prozess lediglich für UI-Test-Automatisierungen beim Testen benötigt. Hier wird auch eine Auto-Login-Möglichkeit angeboten, so dass entsprechend eine Interaktive Session für den Test auch nach einem Neustart der VM zur Verfügung steht. In allen anderen Fällen ist grundsätzlich der Betrieb als Service vorzuziehen. Die kritische Frage hierbei ist wiederum, unter welchem Benutzer der Agent-Prozess läuft. Wichtig zu erwähnen ist hierbei nochmals, dass die Prozess-Identity nicht mit Azure DevOps kommuniziert.
Dies wird über ein separates Token mit einer dedizierten Azure DevOps Identity gelöst. Der Prozess-Benutzer ist entscheidend für die Interaktion mit lokalen oder verteilten Ressourcen wie zum Beispiel dem Dateisystem oder Netzwerkverzeichnissen. Auch hier gilt der Ansatz des Least-Privilege Prinzips, und dem Agent-Prozess-Benutzer sollten so wenig Berechtigungen wie möglich zugeteilt werden.
Unterhalt und Wartung
Sind die Agents nun registriert, können Sie von den Pipeline-Runs verwendet werden. Hier ist jedoch noch nicht Schluss mit den Tätigkeiten des Azure-DevOps-Verantwortlichen. Es versteht sich von selbst, dass die virtuellen Maschinen respektive Docker-Images, in denen der Agent betrieben wird, entsprechend aktuell gehalten werden sollen. Genau aus diesem Grund empfiehlt es sich, über IaC eine Automatisierung zu implementieren und in der gebotenen Frequenz die Basis-Images mit Betriebssystem- und Security-Updates zu versorgen.
Das Agent Update selbst kann mittlerweile komplett über Azure DevOps automatisiert werden. Wie in Bild 7 ersichtlich ist, können unter den Agent-Pool-Settings auf der Organization-Ebene die Einstellungen zu automatischem Update sowie Maintenance-Jobs hinterlegt werden.

Maintenance-Job-Konfiguration (Bild 7)
AutorDer Maintenance-Job räumt die Arbeitsverzeichnisse des Agents auf, die nicht mehr unter Verwendung stehen. Dies hilft wieder dabei, Festplattenplatz freizuräumen. Je nach Pipeline können aber noch weitere Daten auf der Disk geschrieben werden, welche vom Maintenance-Job nicht erfasst werden. Da wir ohnehin eine Automatisierung zur Erstellung der Agent-VMs oder Containern haben sollten, empfiehlt es sich, die entsprechenden Instanzen von Zeit zu Zeit zu löschen und neu zu erstellen. In der Cloud ist dies mittels Managed DevOps Pools komplett automatisiert und einfach einzurichten.
Fazit
Die Verwaltung von selbst gehosteten Azure DevOps Agents erfordert eine sorgfältige Planung und stellt einen gewissen Aufwand hinsichtlich der Wartung dar. Die Pipeline-Infrastruktur wird somit aber robuster und sicherer.
Azure DevOps-Server-Anwender haben hier ein wenig das Nachsehen, da einzig die Option zur Verfügung steht, alles selbst zu konfigurieren und zu betreiben. Azure DevOps Services können die Managed DevOps Pools in Azure nutzen, was abgesehen von ein wenig Konfiguration keinen großen Aufwand mit sich bringt und einem dann eine Managed-Pipeline-Infrastruktur zur Verfügung steht.
Die Agents sind den Zwecken und Verantwortlichkeiten nach strikt zu trennen. Somit ist man bereits bei einem kleinen Setup mit mindestens drei Agent Pools unterwegs. Entsprechend empfiehlt es sich, von Anfang an die Installation und Konfiguration der Agents zu automatisieren und die Benutzer und Rollen immer nach dem Least-Privilege-Prinzip einzurichten.
Nicht zu vergessen ist der Unterhalt der Agent-Infrastruktur. Die Basis-Images müssen kontinuierlich mit Betriebssystem- und Security-Updates aktualisiert werden. Die neue Option von Microsoft, mittels Maintenance-Jobs die nicht mehr verwendeten Arbeitsverzeichnisse aufzuräumen, ist gut und recht, ersetzt aber nicht das periodische Neuerstellen von Agent-Umgebungen mit Betriebssystem-Updates in einem frischen System.