Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Lesedauer 20 Min.

Sicherheit in jeder Zeile Code

Wie Sie mit GitHub Advanced Security for Azure DevOps neu geschriebenen Code proaktiv und automatisiert auf Schwachstellen überprüfen und diese effizient beseitigen.
Sicherheit und Künstliche Intelligenz sind aktuelle Kernthemen der heutigen Softwareentwicklung. Darum verwundert es niemanden, dass GitHub sich genau diese zwei Themen auf die Fahne geschrieben hat und die eigenen Produkte darauf ausrichtet. Natürlich kommen auch die anderen Dev­Ops-Themen wie Code-Verwaltung, CI/CD-Automatisierung, Arbeitsplanung und Artefakte nicht zu kurz, doch ist dies mittlerweile die Basis, die jede DevOps-Umgebung heutzutage aus dem Effeff beherrschen sollte.Doch warum sprechen wir zum Beginn des Artikels über GitHub, obwohl es um DevSecOps mit Azure DevOps geht? Die Antwort ist ganz einfach und zeigt auch die neue Strategie von Microsoft: Mit dem Kauf und der Weiterentwicklung von GitHub hat Microsoft zusammen mit Azure DevOps zwei mehr oder weniger konkurrierende DevOps-Plattformen im eigenen Portfolio. GitHub ist ganz klar das Zugpferd in Sachen Innovation, und mit dem Themenfokus Sicherheit und KI ist die DevSecOps-Funktionalität ebenfalls ganz klar bei GitHub angesiedelt. Azure DevOps dagegen ist bei vielen großen Unternehmen noch immer als die firmenweite Dev­Ops-Umgebung gesetzt, und auch hier wird eine moderne, ­effiziente und automatisierte Sicherstellung der Sicherheit der zu entwickelnden Applikationen benötigt. Anstatt nun zwei Lösungen zu entwickeln, hat sich Microsoft dazu entschieden, die Advanced-Security-Funktionen von GitHub in Azure DevOps zu integrieren. Technisch laufen also in beiden Plattformen dieselben Security-Dienste, jedoch hat jede Plattform die eigene Integration in die Benutzeroberfläche sowie die Verwaltung von Alerts implementiert.

DevSecOps

Bevor wir die Funktionen von GitHub Advanced Security for Azure DevOps (kurz GHAzDO) zusammen anschauen, sollten wir erst die Grundlagen klären, damit wir das Ganze richtig einordnen können. Mit GHAzDO bekommen wir Funktio­nen und Services, um Sicherheitslücken in Codestellen, Abhängigkeiten oder gar versehentlich veröffentlichte Geheimnisse zu erkennen, zu eskalieren und deren Beseitigung zu überwachen. Kurzum: Wir erweitern unseren DevOps-Prozess zu einem DevSecOps-Prozess.DevSecOps ist eine Erweiterung von DevOps, um Sicherheitspraktiken in den DevOps-Ansatz zu integrieren. Im Gegensatz zum traditionellen Modell mit einem zentralen Sicherheitsteam wird jedes Entwicklungsteam befähigt, die notwendigen Sicherheitskontrollen in den Entwicklungsprozess einzubinden. Diese Sicherheitspraktiken werden früh im Entwicklungszyklus durchgeführt, was als „Shift Left“-Ansatz bezeichnet wird. Wie bei allem in einem optimalen Dev­Ops-Prozess geht es also auch hier darum, möglichst früh­zeitig Feedback an die Entwickler zu geben. Somit wird der frühere Roundtrip zu und vom Security-Team verkürzt oder gar weggelassen, und die Sicherheitslücken können effizient während der Entwicklung geschlossen werden.Generell wird in einem DevSecOps-Prozess die Sicherheit in den folgenden drei Hauptbereichen getestet:
  • Statische Analyse: Bei der statischen Analyse, auch als Static Application Security Testing (SAST) bekannt, wird der Sourcecode auf Fehler und Schwachstellen überprüft. Das Tool hat vollen Zugriff auf den Sourcecode.
  • Softwarezusammensetzung: Mittels der Analyse der Zusammensetzung der Software, auch als Software Composition Analysis (SCA) bekannt, werden die Abhängigkeiten der Software auf Vulnerabilities überprüft. Gerade bei modernen Frameworks und Applikationen ist die Anzahl der Open-Source-Bibliotheken sehr groß und unübersichtlich, sodass es für Angreifer ein leichtes Spiel ist, über diesen Kanal etwas einzuschleusen. Umso wichtiger ist es, diese Abhängigkeiten automatisiert und permanent zu überprüfen.
  • Dynamische Analyse: Im Unterschied zu den zwei vorherigen Bereichen wird hierbei die Software während der Laufzeit analysiert. Die Tools agieren als virtuelle Penetration-Tester und versuchen, die Software über bekannte Schwachstellen zu „hacken“. Bei dieser Form der Sicherheitsana­lyse wird zwischen Dynamic Application Security Testing (DAST) und Interactive Application Security Testing (IAST) unterschieden. Bei der ersteren Analyseform wird die Applikation als Blackbox angesehen, und es wird lediglich von außen getestet. Die zweite Variante kombiniert den Blackbox- und Whitebox-Ansatz, hat Kenntnisse über das Innenleben und kann die Applikation daher während des Tests von innen überwachen (Hook Points, Debugger, Monitoring, Interceptors und anderes).
Programmierfehler sind nach wie vor der Attack-Vektor Nummer eins, wenn es darum geht, Systeme anzugreifen. Die obigen Mechanismen helfen dem Softwareentwicklungs­team, diese Fehler möglichst früh zu erkennen und zu beseitigen. Im Idealfall wird diese Überprüfung als zwingende Policy in den Pull-Request-Workflow integriert, sodass niemals ungetesteter Code integriert werden kann. Viele moderne Softwareumgebungen setzen stark auf das Einbinden von Dritthersteller- oder Open-Source-Bibliotheken. Diese wiederum haben ihre eigenen Abhängigkeiten und so weiter. Kurzum, der Abhängigkeitsbaum einer moderat komplexen Applikation ist schon zu groß, als dass dieser manuell überprüft werden könnte. Zudem werden Schwachstellen in Abhängigkeiten oft erst Tage oder Monate nach der Veröffentlichung entdeckt. Somit ist eine automatische und periodische Überprüfung aller Abhängigkeiten Pflicht. Die technische Überprüfung an sich ist nicht allzu komplex, jedoch ist der Unterhalt einer stets aktualisierten Vulnerability-Datenbank das A und O für den Erfolg eines solchen Dienstes.GitHub Advanced Security hat sich auf den statischen Teil der Sicherheitsüberprüfungen spezialisiert. Mittels Code Scanning wird der Sourcecode auf Schwachstellen gescannt, und mittels Dependency Scanning werden die Abhängigkeiten wie zum Beispiel NuGet-Pakete oder NPM-Pakete anhand einer Vulnerability-Datenbank überprüft. Mittels Secret Scanning kann der Code nach Passwörtern oder Schlüsseln durchsucht werden oder gar der Git-Push verweigert werden, sollten sich Secrets in den Code-Fragmenten befinden. Im Bereich der dynamischen Analyse ist derzeit keine Standardfunktionalität vorhanden, da diese Tools oft spezifisch auf die Applikation hin evaluiert und konfiguriert werden müssen. Es lässt sich jedoch problemlos mittels Pipelines ein DAST/IAST-Tool integrieren, und Sicherheitslücken können ebenso in das zentrale Alert-Management integriert werden.Bevor wir nun in die Details der drei Bereiche eintauchen, müssen wir erst klären, wie GHAzDO aktiviert und konfiguriert wird.

GitHub Advanced Security for Azure DevOps

Die Nutzung von GitHub Advanced Security for Azure Dev­Ops ist an zwei Bedingungen geknüpft: das Vorhandensein von Azure DevOps Services – es steht also nur Kunden der Cloud-Lösung zur Verfügung – sowie einer dedizierten Lizenz pro Benutzer.Die Lizenzierung läuft vollautomatisiert über die Abrechnung von Azure DevOps über eine Azure Subscription ab. Der Lizenzpreis ist identisch mit den Preisen von GitHub für Advanced Security. Tatsächlich wird dasselbe Feature einfach mit der Integration in Azure DevOps oder GitHub bezahlt und genutzt. Die Aktivierung erfolgt über das Repository, und die Lizenzen werden pro aktivem Committer über die letzten 90 Tage berechnet. Natürlich wird innerhalb derselben Organisation die Lizenz pro Benutzer nur einmal verrechnet. Ist man also aktiver Committer in fünf Repositories in der gleichen Organisation, bezahlt man die Lizenz für einen Benutzer ein Mal. Der Lizenzpreis schlägt mit 49 US-Dollar pro Monat und aktiven Committer, verglichen mit der Basic-Lizenz von 6 US-Dollar pro Monat, eher etwas teuer zu Buche. Vergleicht man ihn aber mit anderen Anbietern in diesem Bereich, liegt er durchaus im üblichen Rahmen.Advanced Security kann pro Repository respektive für alle Repositories in einem Projekt oder sogar in der gesamten ­Organisation aktiviert werden. Das Aktivieren des On/Off-Schalters wird durch einen Dialog unterbrochen, der die Anzahl zu zahlender Lizenzen berechnet und eine erneute Bestätigung erfordert. Danach sind die Lizenzen im Untermenü Billing in den Einstellungen ersichtlich. Bild 1 zeigt den Dialog bei der Aktivierung auf einem Repository.
Aktivierung von Advanced Security auf einem Repository (Bild 1) © Autor
Mit der Aktivierung von Advanced Security bekommen wir unter dem Menüeintrag Repos ein neues Untermenü Advanced Security (siehe Bild 2). Hier werden zentral alle Alerts dargestellt und können entsprechend gesichtet und verarbeitet werden. Die Funktionalität integriert sich in das Permission-System von Azure DevOps, und es lässt sich feingranular auf Gruppen- und Benutzerebene einstellen, wer Alerts sehen oder diese auch manuell schließen darf. Die Alert-Übersicht zeigt die Alerts der jeweiligen Security Scanner: Dependency Scanning, Code Scanning und Secret Scanning. Für jeden Alert kann eine Detailansicht geöffnet werden, die Zusatz­informationen zur eigentlichen Vulnerability anzeigt sowie die Details, wo genau die Sicherheitslücke aufgetreten ist beziehungsweise welcher Code-Abschnitt dafür verantwortlich ist. Grundsätzlich werden diese Alerts automatisch geschlossen, wenn ein Folge-Build aufzeigt, dass die Abhängigkeit aktualisiert wurde respektive der Code korrigiert wurde. Für den Fall, dass der Alert nicht behandelt werden soll, kann dieser auch manuell geschlossen werden. Hierbei muss eine Begründung angegeben werden. Die Detailansicht sowie das manuelle Schließen des Alerts sind in Bild 3 dargestellt.
Übersicht über Advanced Security Alerts (Bild 2) © Autor
Details einer Alert-Meldung (Bild 3) © Autor
Advanced Security verwendet das SARIF-Format, um die Sicherheitslücken zu melden. Da dies ein standardisiertes Format ist, können sich auch Drittanbieter-Tools integrieren und zusätzliche Alerts darstellen. Wichtig zu erwähnen ist hierbei noch, dass nur auf die Alerts zugegriffen werden kann, solange auch eine Lizenz vorhanden ist. Wird die Lizenzierung für Advanced Security beendet oder unterbrochen, erlischt auch der Zugriff auf die Alerts.Nun sind wir fast so weit, um mit der Konfiguration von Advanced Security zu starten. Da das Code- und Dependency-Scanning auf Azure Pipelines basiert, müssen wir sicherstellen, dass unsere Agents dafür gerüstet sind. Im Fall von Hosted Agents muss nichts unternommen werden; diese haben bereits alle notwendigen Konfigurationen und Komponenten installiert. Im Fall von Self-Hosted Agents müssen folgende Punkte sichergestellt werden: Während der Ausführung der Scans muss der Agent auf die Vulnerability-Advisory-Datenbank zugreifen können. Hierzu sind einige Domains respektive IP-Adressen auf den Firewall-Einstellungen der Agents respektive des Agent-Netzwerks zu erlauben. Zusätzlich benötigt der Agent eine .NET-6.0.x-Runtime, und das CodeQL-Bundle muss installiert sein beziehungsweise installiert werden können. Haben die Agents keinen Zugriff auf die Download-Seiten respektive auf das Internet, so müssen die Agents zuvor mit den entsprechenden Runtimes und Tools ausgestattet werden. Detaillierte Informationen zum Setup der Agents stellt Microsoft unter [1] bereit.

Secret Scanning

Das Secret Scanning wird automatisch aktiviert, wenn auf einem Repository (respektive über die projekt- oder organisationsweiten Einstellungen) Advanced Security eingeschaltet ist. Die Push Protection lässt sich jedoch per Einstellung aktivieren oder deaktivieren (Bild 4).
Block Secrets Push (Bild 4) © Autor
Unfreiwillig veröffentlichte Secrets, also Passwörter, Access Tokens und Keys, sind eine große Bedrohung, da sich ein potenzieller Angreifer durch sie mit echten Authentisierungsschlüsseln ausweisen kann. Schnell ist es passiert und ein Entwickler hat unbeabsichtigt, nach dem raschen Ausprobieren mit in den Sourcecode kopierten Secrets, den Commit gepusht, ohne diese zuvor wieder entfernt zu haben. Das Secret ist nun in der Git-Historie und auch über die Volltextsuche auffindbar. Hier kommt das Secret Scanning ins Spiel. Sollte ein Secret im Sourcecode gefunden werden, so wird ­direkt ein Alert erstellt, und der Entwickler bekommt eine ­detaillierte Anleitung, was zu tun ist und wie dies aus der Git-Historie zu entfernen ist.Die Secrets in der Git-Historie zu entfernen ist mühsam, und auch der Revocation-Prozess ist mit Aufwand verbunden. Besser ist es also, wenn das Secret erst gar nicht erst den Entwicklerrechner verlässt. Dies wird mittels der Push Protection verhindert. Die Push Protection hängt sich serverseitig in den Git-Push-Event und analysiert den Dateiinhalt der gepushten Objekte. Wird ein Secret erkannt, so wird der Push nicht akzeptiert und der Vorgang abgebrochen. Daraus resultiert, dass die Secrets nicht serverseitig gespeichert werden und somit nicht für andere zugänglich sind. Das Problem kann also lokal gelöst werden und danach – ohne Secrets – nochmals gepusht werden. Die Push Protection funktioniert jedoch nur mit Secrets, bei denen mit einer hohen Wahrscheinlichkeit identifiziert wurde, dass es sich tatsächlich um ein Secret handelt. Das bedeuetet: Die Push Protection findet weniger Se­crets als das Scanning im Repository.Das Secret Scanning unterscheidet bei den Secrets zwischen Provider Patterns und Non-Provider Patterns. Microsoft listet unter [2] die einzelnen Provider Patterns und Non-Provider Patterns detailliert auf, inklusive der Angabe, welche Patterns in der Push Protection unterstützt sind und welche als User Alerts gefunden werden können.Was ist nun zu unternehmen, wenn ein Secret erkannt wurde? Im Fall eines User Alerts ist das Secret bereits auf dem Server gespeichert und somit für andere zugänglich. Hier muss erstens das Secret widerrufen werden. Je nach Secret und Anbieter kann dies über ein API durchgeführt werden respektive das Passwort oder dergleichen sofort muss aktualisiert werden. Nachdem das kompromittierte Secret nicht mehr gültig ist, kann der entsprechende Alert geschlossen werden. Falls gewünscht und möglich, kann das Secret noch aus der serverseitigen Historie gelöscht werden. Bei Feature-Branches ist dies relativ einfach; bei gemeinsam verwendeten Integrations-Branches ist es dagegen aufwendiger, und der Nutzen dieser Aktion muss beurteilt werden.Bei einem Secret, das durch die Push Protection blockiert wird, muss dies lokal korrigiert werden. Das Secret ist somit nur auf dem Entwicklerrechner vorhanden und nicht für ­andere ersichtlich. Somit müssen wir das Secret aus dem Sourcecode und der lokalen Git-Historie entfernen. Einfach einen neuen Commit mit dem Entfernen des Secrets zu erstellen würde nichts bringen, da das Secret dennoch in der Commit-Historie vorhanden wäre.Wurde das Secret auf dem letzten Commit des Pushs hinzugefügt, genügt es, mittels git commit -amend den letzten Commit zu ersetzen. Liegt das Einfügen des Secrets mehrere Commits zurück, so muss diese Historie umgeschrieben werden. Mittels git rebase -i [commit ID before credential introduction]~1 kann ein interaktiver Rebase durchgeführt werden. Mittels pick und edit können die Commits editiert und das Secret entfernt werden. Der Commit mit dem Secret kann mittels git commit --amend ersetzt werden, und der Rebase kann dann mit git rebase --continue fortgesetzt werden.Nun gibt es noch die letzte Kategorie, bei denen ein Alert eintreten kann: False-Positives oder Beispielwerte in den Konfigurationsdateien. Bei Beispielwerten oder Platzhaltern sollte der Wert immer mit dem Präfix Placeholder definiert werden. Somit sollte dies klar als Platzhalter erkannt und nicht als Secret identifiziert werden. Für die anderen Fälle ist es möglich, den Push zu forcieren. Hierzu muss die Commit-Message den String skip-secret-scanning:true beinhalten. Hiermit wird die Push-Protection für diesen Commit ausgeschaltet.

Dependency Scanning

Das Dependency Scanning ist ein Pipeline-basiertes Scanning-Tool. Es funktioniert mittels YAML-Pipelines und Classic Builds. Im Artikel wird nur auf die YAML-Pipeline-Variante eingegangen, die Funktionsweise für die Classic Builds ist jedoch identisch. Einer Pipeline muss lediglich der Task AdvancedSecurity-Dependency-Scanning@1 hinzugefügt werden. Das klingt erst einmal einfach, und es ist auch so.Beim Dependency Scanning werden alle Abhängigkeiten, direkt und transient, mit einer Vulnerability-Datenbank überprüft. Sollte eine Vulnerability gefunden werden, so wird ein entsprechender Alert erstellt. Advanced Security Dependency Scanning verwendet die GitHub Advisory Database, die unter [3] öffentlich verfügbar ist. Die Datenbank unterstützt die gängigsten Package-Manager wie zum Beispiel NuGet, NPM und viele mehr.Der zu Anfang erwähnte Task geht also für die unterstützten Package-Manager den Dependency Tree der Applika­tion durch und prüft jede Dependency mit der effektiven Version gegenüber der Datenbank.Für die Pipeline, die den Dependency Scan ausführt, gibt es einige Punkte zu beachten. Einerseits sollten die Abhängigkeiten vor der Ausführung dieses Tasks wiederhergestellt worden sein. Bei .NET sollte also ein dotnet restore oder bei JavaScript-Projekten ein npm install ausgeführt worden sein. Zudem ergibt es Sinn, diese Pipeline nicht nur mittels eines Push-Triggers, sondern auch periodisch (zum Beispiel täglich) auszuführen. Auch ohne Code-Änderungen wollen wir über neu bekannt gewordene Vulnerabilities informiert werden.nIst nun ein Alert vorhanden, gilt es diesen zu behandeln. Im einfachsten Fall gibt es bereits eine neuere Version der Abhängigkeit, die die Sicherheitslücke schließt. Somit muss die Version aktualisiert werden, und der Alert wird automatisch geschlossen. Falls es keine Aktualisierung gibt, muss das Vorgehen projektspezifisch analysiert und entschieden werden. Entweder wird das Risiko getragen, oder es wird eine Alternative evaluiert. Im ersten Fall kann der Alert manuell geschlossen werden.Einige GitHub-Nutzer:innen werden sich nun fragen, wieso die Version des Pakets manuell ak­tua­lisiert werden muss, denn GitHub bietet mit Dependabot einen Auto­matismus, der automatisch einen Pull-Request mit dem Dependency-Update erstellt. Im Fall einer kompletten CI/CD-Automatisierung wird das Ganze im Pull-Request direkt noch automatisiert getestet, und der Aufwand für den Entwickler reduziert sich auf den Review und das Approval. Diese Feature ist leider (noch) nicht in Azure DevOps verfügbar.

Code Scanning

Das Code Scanning ist wie das Dependency Scanning ein Pipeline-basiertes Scanning-Tool. Entsprechend muss eine Pipeline mit den jeweiligen Tasks erweitert werden. Das Code Scanning beinhaltet drei Schritte:
  • Initialisierung mittels AdvancedSecurity-Codeql-Init@1
  • Erstellen der Applikation mittels Auto-Build oder eigenen CI-Build-Tasks (zum Beispiel dotnet build)
  • Analyse mittels AdvancedSecurity-Codeql-Analyze@1
Die Code-Scanning-Pipeline kann problemlos mit dem Dependency Scanning kombiniert werden und ist oftmals der empfohlene Weg, um beide Scans in derselben Pipeline durchzuführen. Gehen wir zunächst aber nochmals auf das Code Scanning ein. Advanced Security verwendet CodeQL als Abfragesprache, um den Code auf Schwachstellen zu untersuchen. Bei CodeQL handelt es sich um eine neue – oder wie es von Microsoft angekündigt wurde: revolutionäre – semantische Code Engine. CodeQL wurde während 13 Jahren von 30 Personen an der Oxford University entwickelt und hat schlussendlich über Akquisen den Weg zu GitHub gefunden. Der Sourcecode wird von der Engine analysiert und als Datenbank exportiert. Diese Datenbank kann nun mit der Abfragesprache CodeQL durchsucht werden. Eine Vulnerabi­lity wird also gefunden, wenn eine entsprechende Query ein oder mehrere Resultate zurückgibt. Die CodeQL-Engine muss dazu auf dem Agent vorhanden sein. Wie zu Beginn des Artikels erläutert, kann diese automatisch über den Task oder manuell auf dem Agent installiert werden. Der Init-Task dient zur Konfiguration der Engine und zum Aufbau der Datenbanken. Bei kompilierten Sprachen hängt sich die Engine an den Kompilationsprozess und baut so diese Datenbank auf. Deshalb auch das Dreierkonstrukt mit Init, Build und Analyse in der Pipeline. Microsoft hat die Konfiguration dieser Tasks einfach gehalten, und der Init-Task ist mehr oder weniger der einzige Task, der entscheidende Konfigurationswerte erlaubt. Mitunter wird hier auch die Query Suite definiert, also die Queries, mit denen nach Vulnerabilities gesucht wird. Listing 1 zeigt den Aufbau einer solchen Pipeline anhand einer einfachen ASP.NET-Core-Applikation. Advanced Security bietet auch einen Auto Build-Task an, bei dem versucht wird, die Technologie zu erkennen und einen Build durchzuführen. In der Praxis klappt dies jedoch oft nicht, da die meisten Applikationen spezifische Anforderungen an den Build mit sich bringen. Darum werden die eigentlichen Build-Steps der Applikation von Init und Analyze umschlossen. Im Code von Listing 1 sind noch weitere Tasks vorhanden, auf die wir gleich näher eingehen werden. Mit unserer Pipeline und den drei Advanced-Security-Tasks AdvancedSecurity-Codeql-Init@1, AdvancedSecurity-Dependency-Scanning@1 und AdvancedSecurity-Codeql-Analyze@1 haben wir eine funktionierende Pipeline, die unseren Code und die Dependencies untersucht und bei Sicherheitslücken entsprechende Alerts erstellt.
Listing 1: Azure-Pipelines mit GHAzDO
# Trigger the pipeline <span class="hljs-keyword">on</span> <span class="hljs-keyword">changes</span> <span class="hljs-keyword">to</span> the main branch<br/>trigger:<br/>- main<br/><br/># Define the build environment<br/>poo<span class="hljs-variable">l:</span><br/>  vmImage: <span class="hljs-string">'ubuntu-latest'</span><br/><br/># Define variables used in the pipeline<br/>variable<span class="hljs-variable">s:</span><br/>  buildConfiguration: <span class="hljs-string">'Release'</span><br/>  workingDirectory: <br/>    <span class="hljs-string">'$(Build.SourcesDirectory)/HelloWorld'</span><br/>  # DependencyScanning.Timeou<span class="hljs-variable">t:</span> <span class="hljs-number">600</span> # default <span class="hljs-keyword">is</span> <span class="hljs-number">300</span>, <br/>  # anything under <span class="hljs-number">300</span> <span class="hljs-built_in">has</span> <span class="hljs-keyword">no</span> effect<br/>  # DependencyScanning.Skip: true # <span class="hljs-keyword">break</span>-glass <br/>  # scenario <span class="hljs-keyword">to</span> skip dependency scanning from blocking<br/>  # the build<br/><br/># Pipeline steps<br/>step<span class="hljs-variable">s:</span><br/># Initialize Advanced Security CodeQL<br/>- task: AdvancedSecurity-Codeql-Init@<span class="hljs-number">1</span><br/>  <span class="hljs-built_in">input</span><span class="hljs-variable">s:</span><br/>    <span class="hljs-keyword">language</span><span class="hljs-variable">s:</span> <span class="hljs-string">'csharp'</span><br/>    querysuite: <span class="hljs-string">'security-extended'</span><br/>  displayName: <span class="hljs-string">'Initialize CodeQL'</span><br/><br/># Install .NET Core SDK<br/>- task: UseDotNet@<span class="hljs-number">2</span><br/>  <span class="hljs-built_in">input</span><span class="hljs-variable">s:</span><br/>    packageType: <span class="hljs-string">'sdk'</span><br/>    <span class="hljs-keyword">version</span>: <span class="hljs-string">'8.x'</span><br/>    installationPath: $(Agent.ToolsDirectory)/dotnet<br/>  displayName: <span class="hljs-string">'Install .NET Core SDK'</span><br/><br/># Restore dependencies using dotnet <span class="hljs-keyword">command</span><br/>- <span class="hljs-keyword">scrip</span><span class="hljs-variable">t:</span> dotnet restore $(workingDirectory)/<br/>    HelloWorld.sln<br/>  displayName: <span class="hljs-string">'Restore dependencies'</span><br/>  workingDirectory: $(workingDirectory)<br/><br/># Build the solution using dotnet <span class="hljs-keyword">command</span><br/>- <span class="hljs-keyword">scrip</span><span class="hljs-variable">t:</span> dotnet build $(workingDirectory)/<br/>    HelloWorld.sln --configuration <br/>    $(buildConfiguration) --<span class="hljs-keyword">no</span>-restore<br/>  displayName: <span class="hljs-string">'Build solution'</span><br/>  workingDirectory: $(workingDirectory)<br/><br/># Run unit tests using dotnet <span class="hljs-keyword">command</span><br/>- <span class="hljs-keyword">scrip</span><span class="hljs-variable">t:</span> dotnet test $(workingDirectory)/<br/>    HelloWorld.sln <br/>    --configuration $(buildConfiguration) --<span class="hljs-keyword">no</span>-build <br/>    --logger trx<br/>  displayName: <span class="hljs-string">'Run unit tests'</span><br/>  workingDirectory: $(workingDirectory)<br/><br/># Publish the web application <span class="hljs-keyword">to</span> the artifact staging directory<br/>- <span class="hljs-keyword">scrip</span><span class="hljs-variable">t:</span> dotnet publish $(workingDirectory)/<br/>    HelloWorld/HelloWorld.csproj  <br/>    --configuration$(buildConfiguration) <br/>    --output $(Build.ArtifactStagingDirectory)<br/>  displayName: <span class="hljs-string">'Publish web app'</span><br/>  workingDirectory: $(workingDirectory)<br/><br/># Create <span class="hljs-keyword">an</span> SBOM <span class="hljs-built_in">and</span> <span class="hljs-built_in">add</span> it <span class="hljs-keyword">to</span> the pipeline artifact<br/>- <span class="hljs-keyword">scrip</span><span class="hljs-variable">t:</span> |<br/>    curl -Lo $(Agent.TempDirectory)/sbom-tool <br/>      http<span class="hljs-variable">s:</span>//github.<span class="hljs-keyword">com</span>/microsoft/sbom-tool/releases/<br/>      latest/download/sbom-tool-linux-x64<br/>    chmod +<span class="hljs-keyword">x</span> $(Agent.TempDirectory)/sbom-tool<br/>    <span class="hljs-built_in">mkdir</span> $(Build.ArtifactStagingDirectory)/sbom<br/>    $(Agent.TempDirectory)/sbom-tool generate  <br/>      -<span class="hljs-keyword">b</span> $(Build.ArtifactStagingDirectory)/sbom <br/>      -bc $(Build.SourcesDirectory) -pn HelloWorld <br/>      -pv $(Build.BuildNumber) -<span class="hljs-keyword">ps</span> <span class="hljs-number">4</span>tecture <br/>      -nsb http<span class="hljs-variable">s:</span>//sbom.<span class="hljs-number">4</span>tecture.ch -V Information<br/>  displayName: Generate SBOM  <br/>- task: PublishPipelineArtifact@<span class="hljs-number">1</span><br/>  <span class="hljs-built_in">input</span><span class="hljs-variable">s:</span><br/>    targetPath: <br/>      <span class="hljs-string">'$(Build.ArtifactStagingDirectory)/sbom'</span><br/>    artifac<span class="hljs-variable">t:</span> <span class="hljs-string">'sbom'</span><br/>    publishLocation: <span class="hljs-string">'pipeline'</span><br/>  displayName: Publish SBOM artifact<br/><br/># Publish the web application <span class="hljs-keyword">as</span> <span class="hljs-keyword">a</span> pipeline artifact<br/>- task: PublishPipelineArtifact@<span class="hljs-number">1</span><br/>  <span class="hljs-built_in">input</span><span class="hljs-variable">s:</span><br/>    targetPath: <span class="hljs-string">'$(Build.ArtifactStagingDirectory)'</span><br/>    artifac<span class="hljs-variable">t:</span> <span class="hljs-string">'webapp'</span><br/>    publishLocation: <span class="hljs-string">'pipeline'</span><br/>  displayName: <span class="hljs-string">'Publish Pipeline Artifact'</span><br/><br/># Run Advanced Security Dependency Scanning<br/>- task: AdvancedSecurity-Dependency-Scanning@<span class="hljs-number">1</span><br/>  # <span class="hljs-built_in">input</span><span class="hljs-variable">s:</span><br/>  #   directoryExclusionLis<span class="hljs-variable">t:</span> <span class="hljs-string">'sampledir/</span><br/><span class="hljs-string">  #   ignoreddirectory1;sampledir/ignoreddirectory'</span><br/>  displayName: <span class="hljs-string">'Dependency Scanning'</span><br/><br/># Run CodeQL Analysis<br/>- task: AdvancedSecurity-Codeql-Analyze@<span class="hljs-number">1</span><br/>  displayName: <span class="hljs-string">'CodeQL Analysis'</span><br/><br/># Publish CodeQL Analysis Results<br/>- task: AdvancedSecurity-Publish@<span class="hljs-number">1</span><br/>  displayName: <span class="hljs-string">'Publish CodeQL Results'</span><br/><br/># Add <span class="hljs-keyword">a</span> CSV report <span class="hljs-keyword">for</span> Advanced Security alerts<br/>- task: PowerShell@<span class="hljs-number">2</span><br/>  displayName: <span class="hljs-string">'Generate a csv report based on </span><br/><span class="hljs-string">    Advanced Security alerts'</span><br/>  <span class="hljs-built_in">input</span><span class="hljs-variable">s:</span><br/>    targetType: filePath<br/>    filePath: $(Build.SourcesDirectory)/<br/>      .azure-pipelines/ghazdo-csv-report.ps1<br/>    pwsh: true <br/>  <span class="hljs-keyword">en</span><span class="hljs-variable">v:</span> <br/>    MAPPED_ADO_PAT: $(GHAzDO_PAT)<br/><br/># Conditional step <span class="hljs-keyword">for</span> Pull Request<span class="hljs-variable">s:</span> Run <span class="hljs-keyword">a</span> PowerShell <br/>#   script <span class="hljs-keyword">for</span> additional PR validation<br/>- ${{ <span class="hljs-keyword">if</span> eq(variables[<span class="hljs-string">'Build.Reason'</span>], <br/>    <span class="hljs-string">'PullRequest'</span>) }}:<br/>  - task: PowerShell@<span class="hljs-number">2</span><br/>    displayName: <br/>      <span class="hljs-string">'PR Gating - Additional Checks for Pull Requests'</span><br/>    <span class="hljs-built_in">input</span><span class="hljs-variable">s:</span><br/>      targetType: filePath<br/>      filePath: $(Build.SourcesDirectory)/<br/>        .azure-pipelines/CIGate.ps1<br/>      pwsh: true<br/>    <span class="hljs-keyword">en</span><span class="hljs-variable">v:</span><br/>      MAPPED_ADO_PAT: $(GHAzDO_PAT) 

CodeQL-Queries

In unserer Beispiel-Pipeline verwenden wir im Initialisierungs-Task eine Query Suite mit den Namen security-extended. Microsoft stellt also verschiedene Sammlungen für die Überprüfung des Sourcecodes bereit. Aktuell sind eine Default Suite sowie die Security Extended Suite verfügbar. Eine Übersicht, welche Queries in der Suite vorhanden sind, ist bei GitHub [4] verfügbar. Die Queries selbst sind in einem GitHub-Repository [5] einsehbar.GitHub möchte bewusst andere mit in die Entwicklung der Queries mit einbeziehen. Im Kern stehen die sogenannten GitHub Powered Queries. Hierbei handelt es sich um die wichtigsten Checks in Sachen Security und Vulnerabilities wie OWASP Top 10, SANS 25 sowie andere Best Practices. Zusätzlich werden sogenannte Community Powered Queries mit in die Suiten aufgenommen. Unter Community Queries sind zum Beispiel Queries von Herstellern zu verstehen, welche diese nach dem Bekanntwerden einer Sicherheitslücke veröffentlichen. Microsoft hatte im Jahr 2021 die eigenen Queries als Open-Source-Code veröffentlich, um Solorigate-Attacken zu identifizieren [6]. Natürlich kann jeder respektive jede Firma noch zusätzliche Custom Queries erstellen, die dann in die Code-Scans mit aufgenommen werden. Diese Queries müssen nicht öffentlich sein und können sich im Code-Repository befinden.Den letzteren Fall möchte ich anhand eines einfachen Beispiels veranschaulichen. Unser Szenario lautet wie folgt: In der Vergangenheit ist es immer wieder vorgekommen, dass Entwickler vergessen hatten, kritische API-Controller mit einem Authorize-Attribut zu versehen. Kritische Controller sind in diesem Kontext alle Controller, die unter einer Route mit Präfix /api-secure laufen. Sollte ein Controller mit diesem Pfad konfiguriert sein und kein Authorize-Attribut aufweisen, so wird ein Alert erstellt.Hierzu müssen wir nun eine eigene CodeQL-Query schreiben. GitHub stellt einige Beispiele sowie eine Dokumenta­tion zur Verfügung. Damit wir das nicht im Trial-and-Error-Verfahren mithilfe der Pipelines durchführen müssen, können wir die Queries natürlich lokal entwickeln und ausprobieren. Hierzu benötigen wir das CodeQL-CLI, mit dem wir eine entsprechende Datenbank von unserem Sourcecode erstellen und dann die Queries gegen diese Datenbank ausführen können. Am einfachsten konstruieren wir also den Fehlerfall in unserem Sourcecode und kompilieren die Solution zusammen mit dem CodeQL-CLI, die sich dann an den Compiler hängt und die Datenbank für die semantische Code-Analyse erstellt. Die Query können wir bequem mittels VS Code entwickeln und auch ausführen. Eine entsprechende VS-Code-Extension ist verfügbar, und in Bild 5 sehen wir einen erfolgreichen Query Run mit Resultaten, der gegen eine lokale Datenbank ausgeführt wurde. Die Anleitungen für das CLI sowie die VS-Code-Extension sind unter [7] und [8] verfügbar.
CodeQL in VS Code (Bild 5) © Autor
Kümmern wir uns zunächst aber um unsere Query, die in Listing 2 aufgeführt ist. Als Erstes importieren wir die Module für C# und die Attribute. Danach schreiben wir unsere ­Query, was ein wenig an SQL erinnert. Mit der from-Anweisung durchsuchen wir Klassen und Attribute. In unserem Beispiel ist ja die Kombination von zwei bestimmten Attributen, die nicht zusammen auf der gleichen Klasse zu finden sind, der Ansatz, um diesen Alert auszulösen. Konkret also, wenn ein Controller unter einem bestimmten Pfad läuft und nicht über ein Authorize-Attribut eingeschränkt ist. Wir filtern daher zunächst alle Attribute, die auf der Klasse gesetzt sind (mit getTarget) sowie vom Typ RouteAttribute. Im Fall eines Route-Attributs muss dieses noch als Argument den Wert api-secure aufweisen. Der nächste Schritt ist eine Sub-Query. Wir filtern erneut die Attribute derselben Klasse, dieses Mal jedoch vom Typ AuthorizeAttribute. Diese Sub-Query verwenden wir mit einem not exists-Filter. Somit bekommen wir alle Klassen, die das Route-Attribut mit dem bestimmten Wert gesetzt haben, aber kein Authorize-Attribut aufweisen. Für alle gefundenen Klassen wird dann ein entsprechender Alert erstellt. Was in diesem Alert genau steht, wird über den Kommentar dieser Queries definiert. Hier definieren wir Name, Description sowie die Kategorisierung.
Listing 2: Custom-CodeQL-Query
<span class="hljs-comment">/**</span><br/><span class="hljs-comment"> * @name Secure API controllers without Authorize </span><br/><span class="hljs-comment">   attribute</span><br/><span class="hljs-comment"> * @description Secure controllers within an </span><br/><span class="hljs-comment">   'api-secure' route must have an Authorize </span><br/><span class="hljs-comment">   attribute.</span><br/><span class="hljs-comment"> * @kind problem</span><br/><span class="hljs-comment"> * @problem.severity warning</span><br/><span class="hljs-comment"> * @id demo/api-controller-without-authorize</span><br/><span class="hljs-comment"> * @tags security</span><br/><span class="hljs-comment"> *       api</span><br/><span class="hljs-comment"> */</span><br/> <span class="hljs-keyword">import</span> csharp<br/> <span class="hljs-keyword">import</span> semmle.code.csharp.Attribute<br/> from Class controllerClass, Attribute routeAttribute<br/> <span class="hljs-keyword">where</span> controllerClass = routeAttribute.getTarget()<br/>     <span class="hljs-literal">and</span> routeAttribute.getType().hasName(<br/>       <span class="hljs-string">"RouteAttribute"</span>)<br/>     <span class="hljs-literal">and</span> routeAttribute.getArgument(<span class="hljs-number">0</span>).getValue().<br/>       matches(<span class="hljs-string">"%api-secure/%"</span>)<br/>     <span class="hljs-literal">and</span> <span class="hljs-literal">not</span> exists(Attribute authorizeAttribute |<br/>         authorizeAttribute.getTarget() = <br/>           controllerClass <span class="hljs-literal">and</span><br/>         authorizeAttribute.getType().hasName(<br/>           <span class="hljs-string">"AuthorizeAttribute"</span>)<br/>     )<br/> <span class="hljs-keyword">select</span> controllerClass, <span class="hljs-string">"This controller serving API </span><br/><span class="hljs-string">   a secret route does not have the 'Authorize' </span><br/><span class="hljs-string">   attribute."</span> 
Nun müssen wir diese Query noch in den Code-Scan mit aufnehmen. Wir können die Query zusätzlich zur Standard-Suite ausführen oder ein eigenes Query-Pack definieren. In Listing 3 lagern wir die Konfiguration der CodeQL-Queries in eine YAML-Datei aus, und in Listing 4 verändern wir den AdvancedSecurity-CodeQL-Init-Task so, dass er diese Konfiguration anzieht.
Listing 3: CodeQL-Konfiguration
<span class="hljs-string">name:</span> <span class="hljs-string">"Run custom queries"</span><br/>disable-<span class="hljs-keyword">default</span>-<span class="hljs-string">queries:</span> <span class="hljs-string">false</span><br/><span class="hljs-string">queries:</span><br/>  - <span class="hljs-string">name:</span> Autorized controllers<br/>    <span class="hljs-string">uses:</span> .<span class="hljs-regexp">/.customQueries/</span>authorizedcontrollers.ql 
Listing 4: Custom-CodeQL-Query
<span class="hljs-attribute">steps</span>:<br/># Initialize Advanced Security CodeQL<br/>- <span class="hljs-attribute">task</span>: AdvancedSecurity-Codeql-Init<span class="hljs-variable">@1</span><br/>  <span class="hljs-attribute">inputs</span>:<br/>    <span class="hljs-attribute">languages</span>: <span class="hljs-string">'csharp'</span><br/>    <span class="hljs-attribute">querysuite</span>: <span class="hljs-string">'security-extended'</span><br/>    <span class="hljs-attribute">configfilepath</span>: <span class="hljs-string">'$(build.sourcesDirectory)/</span><br/><span class="hljs-string">      .azure-pipelines/codeqlconfiguration.yml'</span><br/>  <span class="hljs-attribute">displayName</span>: <span class="hljs-string">'Initialize CodeQL'</span> 
Wenn wir nun die Probe aufs Exempel machen und einen neuen Controller für den geschützten Pfad ohne Autorisierungs-Attribut in unserem Feature-Branch einfügen, wird über die PR-Policy unser CI-Build mit Code Scanning gestartet und wir erhalten den Alert, der in Bild 6 dargestellt ist.
Alert von einer Custom Query (Bild 6) © Autor
Mit der Custom-Query-Option können wir uns also nebst den Standard-Checks mit relativ wenig Aufwand auf die Suche nach applikationsspezifischen Sicherheitslücken oder gängigen Programmierfehlern machen. Die firmeneigene Query-Suite kann so zentral für alle Teams gepflegt und stetig erweitert werden.Ein letzter Hinweis zu den Custom-Query-Möglichkeiten: CodeQL ist außerhalb des Forschungsbereichs lizenzpflichtig. Mit der Aktivierung von GitHub Advanced Security / GitHub Advanced Security for Azure DevOps ist eine entsprechende Lizenz vorhanden. Ohne diese Subscription darf das CodeQL-CLI nur für Forschungszwecke verwendet werden.

Pipeline-Erweiterungen

Grundsätzlich haben wir nun die Funktionalität von Advanced Security implementiert und die drei verschiedenen Scans – Code, Dependency und Secrets – über Konfiguration und Pipelines angewendet, und wir erhalten entsprechende Alerts in der Advanced-Security-Ansicht. Jedoch fühlt es sich noch nicht ganz rund an. Wenn wir die Azure-DevOps-Integration von Advanced Security mit der Integration von GitHub vergleichen, fällt einem ziemlich schnell auf, dass bei GitHub nebst dem Dependabot auch eine Pull-Request-Integration vorhanden ist. Dies sind beides Funktionen, welche die Effizienz für die Erkennung und Abarbeitung der Alerts drastisch erhöhen. Viele Teams arbeiten mit einem Pull-Request-Workflow, der über Policies und Pipelines sicherstellt, dass alle Qualitätsanforderungen vor der Integration erfüllt sind. Bei GitHub werden die Alerts, die in einem Pull-Request-Build festgestellt werden, gleich automatisch in den PR hineinkommentiert, und der Pull-Request kann ohne deren Abarbeitung nicht geschlossen werden. Dies hätten wir auch gerne bei Azure DevOps. Leider müssen wir uns damit noch ein wenig gedulden.In der Zwischenzeit können wir uns mit eigenen Skripts in den Pipelines behelfen. Advanced Security ist über das API verfügbar und hat auch dedizierte Permissions, die in den Repository-Einstellungen verwaltet werden können. Somit können wir relativ einfach über das API abfragen, ob es Alerts für den aktuellen PR gibt, und anhand der Alert-Informationen, die Branch, Datei und Zeilennummern enthalten, einen automatisierten PR-Kommentar erstellen. Microsoft hat unter [9] sogar ein GitHub-Repository erstellt, in dem alle diese Skripts als Beispiele vorhanden sind. Über das Einbinden des Power­Shell-Skripts CIGate.ps1 lassen sich automatisch PR-Kommentare erstellen, und der Pull-Request kann blockiert werden, sollten nicht gelöste Alerts vorhanden sein. Das Listing 1 zeigt das Einbinden dieser Skripte in der Pipeline in den letzten zwei Tasks. Böse Zungen würden nun fragen, wieso Microsoft die PR-Integration über Skriptbeispiele zur Verfügung stellt, diese aber nicht wie bei GitHub als Standardfunktion in die Pull-Requests mit aufnimmt und der Benutzer diese Kommentare ohne ein Customizing einfach angezeigt bekommt. Wie immer wird es an den Prioritäten im Backlog gelegen haben, und wir können hoffen, dass Azure DevOps hier bald nachzieht und einen ähnlichen Funktionsumfang im PR bieten wird, wie ihn Advanced Security bei GitHub aufweist.Die Pipeline in Listing 1 beinhaltet noch einen weiteren Task. Auf Zeile 53 ff. wird ein SBOM unserer Software erstellt, der dann im Folgeschritt als Pipeline-Artefakt publiziert wird. Somit können wir unsere Anwender respektive Kunden über die Zusammensetzung, also den gesamten Abhängigkeitsbaum unserer Software, informieren. Der Kunde kann dann anhand dieser Informationen ebenfalls eine Vulnerability-Datenbank durchsuchen. Ein Software Bill of Material, kurz SBOM, ist eine Stückliste aller Komponenten der Software, die sich in einem standardisierten Format exportieren lässt. Microsoft bietet dafür derzeit ein eigenes Tool als CLI an, das auf GitHub verfügbar ist [10] und ohne großen Aufwand in eine Pipeline integriert werden kann.

Fazit

Mit GitHub for Advanced Security bekommen wir ein Dev­Sec­Ops-Tool, das sich ohne großen Konfigurationsaufwand in unsere bestehende Azure-DevOps-Umgebung integriert. Das Softwareentwicklungsteam erhält quasi auf Knopfdruck alle wichtigen Sicherheits-Scans, welche die statische Code-Analyse zu bieten hat. Die Alerts beinhalten die Trace­ability respektive Verlinkung zu den Code-Artefakten. Über das bestehende Berechtigungssystem können die Rechte für das Lesen und Verwalten der Alerts administriert werden. GitHub stellt eine eigene, kuratierte Vulnerability-Datenbank zur Verfügung. Diese kann als vertrauenswürdig betrachtet werden, zumal GitHub als größte Open-Source-Hosting-Plattform an der Quelle sitzt, schnell über Sicherheitslücken informiert zu werden, respektive diese über die eigenen Tools erkennen kann. Ebenfalls wichtig zu erwähnen ist hier, dass Advanced Security für Open-Source-Projekte kostenfrei ist und so die Abhängigkeiten unserer Applikationen immer ­sicherer und stabiler werden. Die Möglichkeit, eigene CodeQL-Queries in die Scans zu integrieren, rundet das Angebot ab. Die nicht vorhandene PR-Integration in Azure DevOps ist dagegen ein klarer Wermutstropfen, zumal Advanced Security in GitHub für den gleichen Preis deutlich mehr Funktionalität und Usability bietet. Hier warten wir gespannt, bis Microsoft diese Funktionen auch bei Azure DevOps integriert.Auch wenn man aufgrund der zusätzlichen Lizenzkosten erstmal ein wenig zurückhaltend ist, lohnt sich diese Investition schnell. Zum einen ist ein möglicher Schadensfall weitaus teurer, und auch der manuelle Überprüfungsaufwand, der mit dieser Automatisierung eingespart werden kann, wird diese Investition in Kürze amortisiert haben.

Fussnoten

  1. [1] Microsoft Learn, Configure GitHub Advanced Security for Azure DevOps, http://www.dotnetpro.de/SL2502DevSecOps1
  2. [2] Microsoft Learn, Secret scanning, http://www.dotnetpro.de/SL2502DevSecOps2
  3. [3] GitHub Advisory Database, https://github.com/advisories
  4. [4] GitHub Docs, C# queries for CodeQL analysis, http://www.dotnetpro.de/SL2502DevSecOps3
  5. [5] codeql-queries auf GitHub, http://www.dotnetpro.de/SL2502DevSecOps4
  6. [6] Microsoft Security Blog, Microsoft open sources CodeQL queries used to hunt for Solorigate activity, http://www.dotnetpro.de/SL2502DevSecOps5
  7. [7] GitHub Docs, About the CodeQL CLI, http://www.dotnetpro.de/SL2502DevSecOps6
  8. [8] GitHub Docs, About CodeQL for VS Code, http://www.dotnetpro.de/SL2502DevSecOps7
  9. [9] GHAzDO-Resources auf GitHub, http://www.dotnetpro.de/SL2502DevSecOps8
  10. sbom-tool auf GitHub, http://www.dotnetpro.de/SL2502DevSecOps9

Neueste Beiträge

DWX hakt nach: Wie stellt man Daten besonders lesbar dar?
Dass das Design von Websites maßgeblich für die Lesbarkeit der Inhalte verantwortlich ist, ist klar. Das gleiche gilt aber auch für die Aufbereitung von Daten für Berichte. Worauf besonders zu achten ist, erklären Dr. Ina Humpert und Dr. Julia Norget.
3 Minuten
27. Jun 2025
DWX hakt nach: Wie gestaltet man intuitive User Experiences?
DWX hakt nach: Wie gestaltet man intuitive User Experiences? Intuitive Bedienbarkeit klingt gut – doch wie gelingt sie in der Praxis? UX-Expertin Vicky Pirker verrät auf der Developer Week, worauf es wirklich ankommt. Hier gibt sie vorab einen Einblick in ihre Session.
4 Minuten
27. Jun 2025
„Sieh die KI als Juniorentwickler“
CTO Christian Weyer fühlt sich jung wie schon lange nicht mehr. Woran das liegt und warum er keine Angst um seinen Job hat, erzählt er im dotnetpro-Interview.
15 Minuten
27. Jun 2025
Miscellaneous

Das könnte Dich auch interessieren

UIs für Linux - Bedienoberflächen entwickeln mithilfe von C#, .NET und Avalonia
Es gibt viele UI-Frameworks für .NET, doch nur sehr wenige davon unterstützen Linux. Avalonia schafft als etabliertes Open-Source-Projekt Abhilfe.
16 Minuten
16. Jun 2025
Mythos Motivation - Teamentwicklung
Entwickler bringen Arbeitsfreude und Engagement meist schon von Haus aus mit. Diesen inneren Antrieb zu erhalten sollte für Führungskräfte im Fokus stehen.
13 Minuten
19. Jan 2017
Evolutionäres Prototyping von Business-Apps - Low Code/No Code und KI mit Power Apps
Microsoft baut Power Apps zunehmend mit Features aus, um die Low-Code-/No-Code-Welt mit der KI und der professionellen Programmierung zu verbinden.
19 Minuten
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige