diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..62d211b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,103 @@ +name: Build and Release + +on: + push: + tags: + - 'v*.*.*' # Trigger on version tags + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: '8.0.x' + + - name: Install dependencies + run: dotnet restore src/AwsServiceAuthenticator.sln + + - name: Build + run: dotnet build --configuration Release src/AwsServiceAuthenticator.sln + + - name: Publish + run: dotnet publish --configuration Release --output ./publish src/AwsServiceAuthenticator.sln + + - name: Archive release files + uses: actions/upload-artifact@v3 + with: + name: release-${{ matrix.os }} + path: ./publish + + create_release: + needs: build + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Download build artifacts (Linux) + uses: actions/download-artifact@v3 + with: + name: release-ubuntu-latest + path: ./publish/ubuntu + + - name: Download build artifacts (Windows) + uses: actions/download-artifact@v3 + with: + name: release-windows-latest + path: ./publish/windows + + - name: Download build artifacts (macOS) + uses: actions/download-artifact@v3 + with: + name: release-macos-latest + path: ./publish/macos + + - name: Create a release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref }} + draft: false + prerelease: false + + - name: Upload release assets (Linux) + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./publish/ubuntu + asset_name: linux-release.tar.gz + asset_content_type: application/gzip + + - name: Upload release assets (Windows) + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./publish/windows + asset_name: windows-release.zip + asset_content_type: application/zip + + - name: Upload release assets (macOS) + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./publish/macos + asset_name: macos-release.tar.gz + asset_content_type: application/gzip diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d6d5f9b --- /dev/null +++ b/.gitignore @@ -0,0 +1,905 @@ +# Created by https://www.toptal.com/developers/gitignore/api/csharp,visualstudio,rider,vs,intellij +# Edit at https://www.toptal.com/developers/gitignore?templates=csharp,visualstudio,rider,vs,intellij + +### Csharp ### +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Intellij Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +# https://plugins.jetbrains.com/plugin/7973-sonarlint +.idea/**/sonarlint/ + +# SonarQube Plugin +# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator-enh.xml +.idea/**/markdown-navigator/ + +# Cache file creation bug +# See https://youtrack.jetbrains.com/issue/JBR-2257 +.idea/$CACHE_FILE$ + +# CodeStream plugin +# https://plugins.jetbrains.com/plugin/12206-codestream +.idea/codestream.xml + +# Azure Toolkit for IntelliJ plugin +# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij +.idea/**/azureSettings.xml + +### Rider ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff + +# AWS User-specific + +# Generated files + +# Sensitive or high-churn files + +# Gradle + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake + +# Mongo Explorer plugin + +# File-based project format + +# IntelliJ + +# mpeltonen/sbt-idea plugin + +# JIRA plugin + +# Cursive Clojure plugin + +# SonarLint plugin + +# Crashlytics plugin (for Android Studio and IntelliJ) + +# Editor-based Rest Client + +# Android studio 3.1+ serialized cache file + +### vs ### +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files + +# User-specific files (MonoDevelop/Xamarin Studio) + +# Mono auto generated files + +# Build results + +# Visual Studio 2015/2017 cache/options directory +# Uncomment if you have tasks that create the project's static files in wwwroot + +# Visual Studio 2017 auto generated files + +# MSTest test Results + +# NUnit + +# Build Results of an ATL Project + +# Benchmark Results + +# .NET Core + +# StyleCop + +# Files built by Visual Studio + +# Chutzpah Test files + +# Visual C++ cache files + +# Visual Studio profiler + +# Visual Studio Trace Files + +# TFS 2012 Local Workspace + +# Guidance Automation Toolkit + +# ReSharper is a .NET coding add-in + +# TeamCity is a build add-in + +# DotCover is a Code Coverage Tool + +# AxoCover is a Code Coverage Tool + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*[.json, .xml, .info] + +# Visual Studio code coverage results + +# NCrunch + +# MightyMoose + +# Web workbench (sass) + +# Installshield output folder + +# DocProject is a documentation generator add-in + +# Click-Once directory + +# Publish Web Output +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted + +# NuGet Packages +# NuGet Symbol Packages +# The packages folder can be ignored because of Package Restore +# except build/, which is used as an MSBuild target. +# Uncomment if necessary however generally it will be regenerated when needed +# NuGet v3's project.json files produces more ignorable files + +# Microsoft Azure Build Output + +# Microsoft Azure Emulator + +# Windows Store app package directories and files + +# Visual Studio cache files +# files ending in .cache can be ignored +# but keep track of directories ending in .cache + +# Others + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) + +# RIA/Silverlight projects + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) + +# SQL Server files + +# Business Intelligence projects + +# Microsoft Fakes + +# GhostDoc plugin setting file + +# Node.js Tools for Visual Studio + +# Visual Studio 6 build log + +# Visual Studio 6 workspace options file + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) + +# Visual Studio LightSwitch build output + +# Paket dependency manager + +# FAKE - F# Make + +# CodeRush personal settings + +# Python Tools for Visual Studio (PTVS) + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio + +# Telerik's JustMock configuration file + +# BizTalk build output + +# OpenCover UI analysis results + +# Azure Stream Analytics local run output + +# MSBuild Binary and Structured Log + +# NVidia Nsight GPU debugger configuration file + +# MFractors (Xamarin productivity tool) working folder + +# Local History for Visual Studio + +# BeatPulse healthcheck temp database + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 + +# Ionide (cross platform F# VS Code tools) working folder + +### VisualStudio ### + +# User-specific files + +# User-specific files (MonoDevelop/Xamarin Studio) + +# Mono auto generated files + +# Build results + +# Visual Studio 2015/2017 cache/options directory +# Uncomment if you have tasks that create the project's static files in wwwroot + +# Visual Studio 2017 auto generated files + +# MSTest test Results + +# NUnit + +# Build Results of an ATL Project + +# Benchmark Results + +# .NET Core + +# ASP.NET Scaffolding + +# StyleCop + +# Files built by Visual Studio + +# Chutzpah Test files + +# Visual C++ cache files + +# Visual Studio profiler + +# Visual Studio Trace Files + +# TFS 2012 Local Workspace + +# Guidance Automation Toolkit + +# ReSharper is a .NET coding add-in + +# TeamCity is a build add-in + +# DotCover is a Code Coverage Tool + +# AxoCover is a Code Coverage Tool + +# Coverlet is a free, cross platform Code Coverage Tool + +# Visual Studio code coverage results + +# NCrunch + +# MightyMoose + +# Web workbench (sass) + +# Installshield output folder + +# DocProject is a documentation generator add-in + +# Click-Once directory + +# Publish Web Output +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted + +# NuGet Packages +# NuGet Symbol Packages +# The packages folder can be ignored because of Package Restore +# except build/, which is used as an MSBuild target. +# Uncomment if necessary however generally it will be regenerated when needed +# NuGet v3's project.json files produces more ignorable files + +# Microsoft Azure Build Output + +# Microsoft Azure Emulator + +# Windows Store app package directories and files + +# Visual Studio cache files +# files ending in .cache can be ignored +# but keep track of directories ending in .cache + +# Others + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) + +# RIA/Silverlight projects + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) + +# SQL Server files + +# Business Intelligence projects + +# Microsoft Fakes + +# GhostDoc plugin setting file + +# Node.js Tools for Visual Studio + +# Visual Studio 6 build log + +# Visual Studio 6 workspace options file + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) + +# Visual Studio 6 technical files + +# Visual Studio LightSwitch build output + +# Paket dependency manager + +# FAKE - F# Make + +# CodeRush personal settings + +# Python Tools for Visual Studio (PTVS) + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio + +# Telerik's JustMock configuration file + +# BizTalk build output + +# OpenCover UI analysis results + +# Azure Stream Analytics local run output + +# MSBuild Binary and Structured Log + +# NVidia Nsight GPU debugger configuration file + +# MFractors (Xamarin productivity tool) working folder + +# Local History for Visual Studio + +# Visual Studio History (VSHistory) files + +# BeatPulse healthcheck temp database + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 + +# Ionide (cross platform F# VS Code tools) working folder + +# Fody - auto-generated XML schema + +# VS Code files for those working on multiple tools + +# Local History for Visual Studio Code + +# Windows Installer files from build outputs + +# JetBrains Rider + +### VisualStudio Patch ### +# Additional files built by Visual Studio + +# End of https://www.toptal.com/developers/gitignore/api/csharp,visualstudio,rider,vs,intellij \ No newline at end of file diff --git a/.idea/.idea.AwsServiceAuthenticator/.idea/.gitignore b/.idea/.idea.AwsServiceAuthenticator/.idea/.gitignore new file mode 100644 index 0000000..ad7f376 --- /dev/null +++ b/.idea/.idea.AwsServiceAuthenticator/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/.idea.AwsTokenRefresherToolkit.iml +/projectSettingsUpdater.xml +/modules.xml +/contentModel.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/.idea.AwsServiceAuthenticator/.idea/.name b/.idea/.idea.AwsServiceAuthenticator/.idea/.name new file mode 100644 index 0000000..b68422f --- /dev/null +++ b/.idea/.idea.AwsServiceAuthenticator/.idea/.name @@ -0,0 +1 @@ +AwsServiceAuthenticator \ No newline at end of file diff --git a/.idea/.idea.AwsServiceAuthenticator/.idea/encodings.xml b/.idea/.idea.AwsServiceAuthenticator/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/.idea/.idea.AwsServiceAuthenticator/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.AwsServiceAuthenticator/.idea/indexLayout.xml b/.idea/.idea.AwsServiceAuthenticator/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/.idea/.idea.AwsServiceAuthenticator/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.AwsServiceAuthenticator/.idea/vcs.xml b/.idea/.idea.AwsServiceAuthenticator/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/.idea.AwsServiceAuthenticator/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/AwsServiceAuthenticator.sln b/AwsServiceAuthenticator.sln new file mode 100644 index 0000000..a531fe4 --- /dev/null +++ b/AwsServiceAuthenticator.sln @@ -0,0 +1,56 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AwsServiceAuthenticator.Cli", "src\AwsServiceAuthenticator.Cli\AwsServiceAuthenticator.Cli.csproj", "{5F16F2F0-DA7B-474F-A40A-46092F08E263}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AwsServiceAuthenticator.Core", "src\AwsServiceAuthenticator.Core\AwsServiceAuthenticator.Core.csproj", "{16A8A4DA-520F-4845-8AE8-7EED515B9A72}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AwsServiceAuthenticator.Infrastructure", "src\AwsServiceAuthenticator.Infrastructure\AwsServiceAuthenticator.Infrastructure.csproj", "{43EC9D03-146A-4F55-9855-592080013F6A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AwsServiceAuthenticator.Commands", "src\AwsServiceAuthenticator.Commands\AwsServiceAuthenticator.Commands.csproj", "{7AA282ED-780C-4B08-BD92-D159A111F47F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".dependencies", ".dependencies", "{4702EE09-5955-4046-B0AE-806AC1637034}" + ProjectSection(SolutionItems) = preProject + .github\workflows\release.yml = .github\workflows\release.yml + README.md = README.md + LICENCE = LICENCE + .gitignore = .gitignore + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{54F0EA9A-90A0-4284-B0FC-0222FF03C1BE}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Integration", "Integration", "{591A044E-8D9F-438F-A967-04A31FA7E034}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AwsServiceAuthenticator.IntegrationTests", "tests\Integration\AwsServiceAuthenticator.IntegrationTests\AwsServiceAuthenticator.IntegrationTests.csproj", "{52E2AF0A-972E-47E5-9A0D-403564831A8F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5F16F2F0-DA7B-474F-A40A-46092F08E263}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5F16F2F0-DA7B-474F-A40A-46092F08E263}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5F16F2F0-DA7B-474F-A40A-46092F08E263}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5F16F2F0-DA7B-474F-A40A-46092F08E263}.Release|Any CPU.Build.0 = Release|Any CPU + {16A8A4DA-520F-4845-8AE8-7EED515B9A72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {16A8A4DA-520F-4845-8AE8-7EED515B9A72}.Debug|Any CPU.Build.0 = Debug|Any CPU + {16A8A4DA-520F-4845-8AE8-7EED515B9A72}.Release|Any CPU.ActiveCfg = Release|Any CPU + {16A8A4DA-520F-4845-8AE8-7EED515B9A72}.Release|Any CPU.Build.0 = Release|Any CPU + {43EC9D03-146A-4F55-9855-592080013F6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {43EC9D03-146A-4F55-9855-592080013F6A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {43EC9D03-146A-4F55-9855-592080013F6A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {43EC9D03-146A-4F55-9855-592080013F6A}.Release|Any CPU.Build.0 = Release|Any CPU + {7AA282ED-780C-4B08-BD92-D159A111F47F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7AA282ED-780C-4B08-BD92-D159A111F47F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7AA282ED-780C-4B08-BD92-D159A111F47F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7AA282ED-780C-4B08-BD92-D159A111F47F}.Release|Any CPU.Build.0 = Release|Any CPU + {52E2AF0A-972E-47E5-9A0D-403564831A8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {52E2AF0A-972E-47E5-9A0D-403564831A8F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {52E2AF0A-972E-47E5-9A0D-403564831A8F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {52E2AF0A-972E-47E5-9A0D-403564831A8F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {591A044E-8D9F-438F-A967-04A31FA7E034} = {54F0EA9A-90A0-4284-B0FC-0222FF03C1BE} + {52E2AF0A-972E-47E5-9A0D-403564831A8F} = {591A044E-8D9F-438F-A967-04A31FA7E034} + EndGlobalSection +EndGlobal diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..bd8d470 --- /dev/null +++ b/LICENCE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Karim + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..4985520 --- /dev/null +++ b/README.md @@ -0,0 +1,165 @@ +# AWS Service Authentication Tool + +## Overview + +Welcome to the AWS Authentication Tool repository! This project contains a .NET application designed to streamline the authentication process for AWS services like CodeArtifact and ECR. This tool helps you log into each service and update necessary tokens or credentials, with detailed logging for easy auditing and troubleshooting. This version is a more robust and versatile alternative to the PowerShell scripts previously provided. + +## Features + +- Authenticate and log into AWS CodeArtifact repositories. +- Authenticate Docker with AWS ECR. +- Cross-platform compatibility (Windows, macOS, and Linux). +- Built with .NET 8 for improved performance and reliability. +- Detailed logging with Serilog. + +## Installation and Usage + +### Prerequisites + +- .NET 8 SDK installed. Download it from the [.NET website](https://dotnet.microsoft.com/download). +- AWS CLI installed and configured. For more information, refer to the [AWS CLI Installation Guide](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) and [Configuring the AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html). +- Docker installed (for ECR authentication). +- Access to the AWS CodeArtifact and ECR services. +- Proper configuration of AWS credentials and region. + +### Installation + +1. Download the latest release from the [Releases](https://github.com/karimz1/aws-authentication-tool/releases) page. + +2. Extract the release package to your desired location. + +### Usage + +Run the tool with the desired command and options: + +```sh +dotnet AwsTokenRefresher.Cli.dll --command nuget --region us-east-1 --logPath ./logs +``` + +#### Available Commands + +- `nuget`: Refreshes authentication tokens for AWS CodeArtifact. +- `ecr`: Refreshes authentication tokens for AWS ECR. +- `--command`: The command to execute (`nuget` or `ecr`). +- `--region`: The AWS region to use (e.g., `us-east-1`). +- `--logFolderPath`: The path to the log file. + +### Automate with Cron Jobs or Task Scheduler + +To ensure that your development machine is always authenticated with the necessary services, you can add this tool to your cron jobs (Linux/macOS) or Task Scheduler (Windows). This way, you can automate the authentication process to run at startup or at regular intervals. + +#### Cron Job Example (Linux/macOS) + +1. Open your crontab configuration: + + ```sh + crontab -e + ``` + +2. Add the following line to run the tool at startup (adjust the path as needed): + + ```sh + @reboot dotnet /path/to/AwsTokenRefresher.Cli.dll --command nuget --region us-east-1 --logPath /path/to/logs + @reboot dotnet /path/to/AwsTokenRefresher.Cli.dll --command ecr --region us-east-1 --logPath /path/to/logs + ``` + +#### Task Scheduler Example (Windows) + +1. Open Task Scheduler and create a new task. +2. Set the trigger to run the task at login. +3. Set the action to start a program and use the following settings: + + ```sh + dotnet C:\path\to\AwsTokenRefresher.Cli.dll --command nuget --region us-east-1 --logPath C:\path\to\logs + ``` + +4. Repeat for the other command if needed. + +### Configuration + +The tool uses configuration variables that can be modified as needed: + +- **REGION**: The AWS region where the service is hosted. Default is `us-east-1`. +- **LOG PATH**: The path to the log file. + +### Example Configuration + +You can set these variables through the command line options as shown in the usage section. + +## Development + +### Prerequisites + +- .NET 8 SDK installed. Download it from the [.NET website](https://dotnet.microsoft.com/download). +- AWS CLI installed and configured. +- Docker installed (for ECR authentication). +- Proper configuration of AWS credentials and region. + +### Setup + +1. Clone the repository: + + ```sh + git clone https://github.com/karimz1/aws-authentication-tool.git + cd aws-authentication-tool + ``` + +2. Build the project: + + ```sh + dotnet build --configuration Release + ``` + +### Running Locally + +Run the tool with the desired command and options: + +```sh +dotnet run --project ./src/AwsTokenRefresher.Cli/AwsTokenRefresher.Cli.csproj -- --command nuget --region us-east-1 --logPath ./logs +``` + +### Testing + +To run the tests, use the following command: + +```sh +dotnet test +``` + +### Building and Releasing + +This repository is set up to use GitHub Actions for continuous integration and deployment. The workflow builds and releases the project for Windows, macOS, and Linux. + +#### GitHub Actions Workflow + +The GitHub Actions workflow is defined in `.github/workflows/release.yml`. + +##### Trigger + +The workflow is triggered on push to tags matching the pattern `v*.*.*` (e.g., `v1.0.0`). + +##### Jobs + +1. **Build**: Builds the project for Windows, macOS, and Linux. +2. **Create Release**: Creates a GitHub release and uploads the built artifacts. + +To create a new release, push a tag to the repository: + +```sh +git tag v1.0.0 +git push origin v1.0.0 +``` + +This will trigger the GitHub Actions workflow, which will build the project for all specified platforms and create a release with the built artifacts. + +## PowerShell Version + +For users who prefer PowerShell, there is a PowerShell version of these scripts available [here](https://github.com/karimz1/AWS-Authentication-Scripts). This version requires PowerShell Core and provides similar functionality for AWS authentication. + +## License + +This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. + +## Contributing + +Contributions are very welcome! If you have suggestions, improvements, or bug fixes, please open an issue or submit a pull request. Let's make this project even better together! \ No newline at end of file diff --git a/src/AwsServiceAuthenticator.Cli/AwsServiceAuthenticator.Cli.csproj b/src/AwsServiceAuthenticator.Cli/AwsServiceAuthenticator.Cli.csproj new file mode 100644 index 0000000..e8a5dba --- /dev/null +++ b/src/AwsServiceAuthenticator.Cli/AwsServiceAuthenticator.Cli.csproj @@ -0,0 +1,25 @@ + + + + Exe + net8.0 + enable + enable + AwsTokenRefresherToolkit + + + + + + + + + + + + + + + + + diff --git a/src/AwsServiceAuthenticator.Cli/Options.cs b/src/AwsServiceAuthenticator.Cli/Options.cs new file mode 100644 index 0000000..363a038 --- /dev/null +++ b/src/AwsServiceAuthenticator.Cli/Options.cs @@ -0,0 +1,15 @@ +using CommandLine; + +namespace AwsTokenRefresher.Cli; + +class Options +{ +[Option('c', "command", Required = true, HelpText = "Command to execute (ecr or nuget).")] +public string Command { get; set; } + +[Option('r', "region", Required = true, HelpText = "AWS region.")] +public string Region { get; set; } + +[Option('l', "logFolderPath", Required = true, HelpText = "Path to log folder.")] +public string LogFolderPath { get; set; } +} \ No newline at end of file diff --git a/src/AwsServiceAuthenticator.Cli/Program.cs b/src/AwsServiceAuthenticator.Cli/Program.cs new file mode 100644 index 0000000..f282806 --- /dev/null +++ b/src/AwsServiceAuthenticator.Cli/Program.cs @@ -0,0 +1,47 @@ +using AwsTokenRefresher.Cli; +using AwsTokenRefresher.Core.Interfaces; +using CommandLine; +using Microsoft.Extensions.DependencyInjection; + + +ILogger logger = null; + +try +{ + ServiceConfiguration.ConfigureAwsLogging(); + ServiceConfiguration.InitializeTraceListeners(); + var logFileName = $"refreshTokens-{DateTime.Now:yyyy-MM-dd}.log"; + + var result = await Parser.Default.ParseArguments(args) + .WithParsedAsync(async options => + { + logFileName = Path.Combine(options.LogFolderPath, logFileName); + var serviceProvider = ServiceConfiguration.ConfigureServices(options.Region, logFileName); + logger = serviceProvider.GetRequiredService(); + + var region = options.Region; + var logPath = options.LogFolderPath; + + logger.LogInformation($"Starting with region: {region}, log path: {logPath}"); + + var command = serviceProvider.GetRequiredService() + .ResolveCommand(options.Command.ToLower()); + + if (command == null) + { + logger.LogError($"Invalid command: {options.Command}"); + return; + } + + await command.ExecuteAsync(); + }); +} +catch (Exception ex) +{ + logger?.LogError($"Unhandled exception: {ex.Message}"); + logger?.LogError($"Stack trace: {ex.StackTrace}"); +} +finally +{ + logger?.LogInformation("Application has exited."); +} \ No newline at end of file diff --git a/src/AwsServiceAuthenticator.Cli/ServiceConfiguration.cs b/src/AwsServiceAuthenticator.Cli/ServiceConfiguration.cs new file mode 100644 index 0000000..5442330 --- /dev/null +++ b/src/AwsServiceAuthenticator.Cli/ServiceConfiguration.cs @@ -0,0 +1,49 @@ +using System.Diagnostics; +using Amazon; +using AwsTokenRefresher.Commands; +using AwsTokenRefresher.Commands.Logic; +using AwsTokenRefresher.Core.Interfaces; +using AwsTokenRefresher.Core.Models; +using AwsTokenRefresher.Infrastructure.Services; +using Microsoft.Extensions.DependencyInjection; +using Serilog; +using ILogger = AwsTokenRefresher.Core.Interfaces.ILogger; + +namespace AwsTokenRefresher.Cli +{ + public static class ServiceConfiguration + { + public static ServiceProvider ConfigureServices(string region, string logPath) + { + var logger = new LoggerConfiguration() + .WriteTo.Console() + .WriteTo.File(logPath) + .CreateLogger(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(_ => new SerilogLogger(logger)); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(_ => new SystemRegion(region)); + serviceCollection.AddSingleton(); + + // Register commands + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + + return serviceCollection.BuildServiceProvider(); + } + + public static void ConfigureAwsLogging() + { + AWSConfigs.LoggingConfig.LogTo = LoggingOptions.Console; + AWSConfigs.LoggingConfig.LogMetrics = true; + AWSConfigs.LoggingConfig.LogResponses = ResponseLoggingOption.Always; + AWSConfigs.LoggingConfig.LogMetricsFormat = LogMetricsFormatOption.JSON; + } + + public static void InitializeTraceListeners() + { + Trace.Listeners.Add(new TextWriterTraceListener(Console.Out)); + } + } +} \ No newline at end of file diff --git a/src/AwsServiceAuthenticator.Commands/AwsServiceAuthenticator.Commands.csproj b/src/AwsServiceAuthenticator.Commands/AwsServiceAuthenticator.Commands.csproj new file mode 100644 index 0000000..d91f762 --- /dev/null +++ b/src/AwsServiceAuthenticator.Commands/AwsServiceAuthenticator.Commands.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + enable + enable + AwsTokenRefresher.Commands + + + + + + + + + + + + diff --git a/src/AwsServiceAuthenticator.Commands/EcrAuthCommand.cs b/src/AwsServiceAuthenticator.Commands/EcrAuthCommand.cs new file mode 100644 index 0000000..4cef408 --- /dev/null +++ b/src/AwsServiceAuthenticator.Commands/EcrAuthCommand.cs @@ -0,0 +1,58 @@ +using System.Diagnostics; +using Amazon; +using Amazon.ECR; +using AwsTokenRefresher.Core.Interfaces; + +namespace AwsTokenRefresher.Commands; + +public class EcrAuthCommand : ICommand +{ + private readonly ILogger _logger; + private readonly string _systemRegion; + + public EcrAuthCommand(ILogger logger, ISystemRegion systemRegion) + { + _logger = logger; + _systemRegion = systemRegion.Region; + } + + public async Task ExecuteAsync() + { + try + { + var ecrClient = new AmazonECRClient(RegionEndpoint.GetBySystemName(_systemRegion)); + var response = + await ecrClient.GetAuthorizationTokenAsync(new Amazon.ECR.Model.GetAuthorizationTokenRequest()); + var authData = response.AuthorizationData[0]; + var token = Convert.FromBase64String(authData.AuthorizationToken); + var credentials = System.Text.Encoding.UTF8.GetString(token).Split(':'); + var username = credentials[0]; + var password = credentials[1]; + + var startInfo = new ProcessStartInfo + { + FileName = "docker", + Arguments = $"login --username {username} --password {password} {authData.ProxyEndpoint}", + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + var process = Process.Start(startInfo); + await process.WaitForExitAsync(); + + if (process.ExitCode == 0) + { + _logger.LogInformation("ECR authentication successful."); + } + else + { + _logger.LogInformation($"ECR authentication failed: {process.StandardError.ReadToEnd()}"); + } + } + catch (Exception ex) + { + _logger.LogInformation($"Error: {ex.Message}"); + } + } +} \ No newline at end of file diff --git a/src/AwsServiceAuthenticator.Commands/Logic/CommandResolver.cs b/src/AwsServiceAuthenticator.Commands/Logic/CommandResolver.cs new file mode 100644 index 0000000..1e21599 --- /dev/null +++ b/src/AwsServiceAuthenticator.Commands/Logic/CommandResolver.cs @@ -0,0 +1,29 @@ +using AwsTokenRefresher.Core.Interfaces; + +namespace AwsTokenRefresher.Commands.Logic; + +public class CommandResolver : ICommandResolver +{ + private readonly IServiceProvider _serviceProvider; + private readonly Dictionary _commands; + + public CommandResolver(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + _commands = new Dictionary + { + { "ecr", typeof(EcrAuthCommand) }, + { "nuget", typeof(NuGetAuthCommand) } + }; + } + + ICommand? ICommandResolver.ResolveCommand(string commandName) + { + if (_commands.TryGetValue(commandName, out var commandType)) + { + return _serviceProvider.GetService(commandType) as ICommand; + } + + throw new ArgumentException("This argument does not exists", nameof(commandName)); + } +} \ No newline at end of file diff --git a/src/AwsServiceAuthenticator.Commands/NuGetAuthCommand.cs b/src/AwsServiceAuthenticator.Commands/NuGetAuthCommand.cs new file mode 100644 index 0000000..d901334 --- /dev/null +++ b/src/AwsServiceAuthenticator.Commands/NuGetAuthCommand.cs @@ -0,0 +1,99 @@ +using System.Diagnostics; +using Amazon; +using Amazon.CodeArtifact; +using Amazon.CodeArtifact.Model; +using AwsTokenRefresher.Core.Interfaces; + +namespace AwsTokenRefresher.Commands; + +public class NuGetAuthCommand : ICommand +{ + private readonly IAwsAuthenticator _awsAuthenticator; + private readonly ILogger _logger; + private readonly string _region; + + public NuGetAuthCommand(IAwsAuthenticator awsAuthenticator, ILogger logger, ISystemRegion region) + { + _awsAuthenticator = awsAuthenticator; + _logger = logger; + _region = region.Region; + } + + public async Task ExecuteAsync() + { + try + { + string domainOwner = await _awsAuthenticator.GetDomainOwnerIdAsync(); + string domainName = await _awsAuthenticator.GetDomainNameAsync(); + string token = await _awsAuthenticator.GetAuthorizationTokenAsync(domainName, domainOwner); + + var client = new AmazonCodeArtifactClient(RegionEndpoint.GetBySystemName(_region)); + var reposResponse = await client.ListRepositoriesInDomainAsync(new ListRepositoriesInDomainRequest + { + Domain = domainName, + DomainOwner = domainOwner + }); + + foreach (var repo in reposResponse.Repositories) + { + string repoName = repo.Name; + await RemoveNuGetSource(repoName); + await AddNuGetSource(domainName, domainOwner, repoName, token); + } + } + catch (Exception ex) + { + _logger.LogInformation($"Error: {ex.Message}"); + } + } + + private async Task AddNuGetSource(string domainName, string domainOwner, string repoName, string token) + { + string nugetSource = $"https://{domainName}-{domainOwner}.d.codeartifact.{_region}.amazonaws.com/nuget/{repoName}/v3/index.json"; + + var addSourceInfo = new ProcessStartInfo + { + FileName = "dotnet", + Arguments = $"nuget add source --name {repoName} --username aws --password {token} {nugetSource}", + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + var process = Process.Start(addSourceInfo); + await process.WaitForExitAsync(); + + if (process.ExitCode == 0) + { + _logger.LogInformation($"NuGet authentication for repository {repoName} successful."); + } + else + { + _logger.LogInformation( + $"NuGet authentication for repository {repoName} failed: {process.StandardError.ReadToEnd()}"); + } + } + + private async Task RemoveNuGetSource(string repoName) + { + var removeSourceInfo = new ProcessStartInfo + { + FileName = "dotnet", + Arguments = $"nuget remove source {repoName}", + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + var removeProcess = Process.Start(removeSourceInfo); + await removeProcess.WaitForExitAsync(); + if (removeProcess.ExitCode == 0) + { + _logger.LogInformation($"Removed existing NuGet source: {repoName}"); + } + else + { + _logger.LogError($"Failed to remove existing NuGet source: {repoName}. Error: {removeProcess.StandardError.ReadToEnd()}"); + } + } +} \ No newline at end of file diff --git a/src/AwsServiceAuthenticator.Core/AwsServiceAuthenticator.Core.csproj b/src/AwsServiceAuthenticator.Core/AwsServiceAuthenticator.Core.csproj new file mode 100644 index 0000000..d3cf999 --- /dev/null +++ b/src/AwsServiceAuthenticator.Core/AwsServiceAuthenticator.Core.csproj @@ -0,0 +1,10 @@ + + + + net8.0 + enable + enable + AwsTokenRefresher.Core + + + diff --git a/src/AwsServiceAuthenticator.Core/Interfaces/IAwsAuthenticator.cs b/src/AwsServiceAuthenticator.Core/Interfaces/IAwsAuthenticator.cs new file mode 100644 index 0000000..a043140 --- /dev/null +++ b/src/AwsServiceAuthenticator.Core/Interfaces/IAwsAuthenticator.cs @@ -0,0 +1,8 @@ +namespace AwsTokenRefresher.Core.Interfaces; + +public interface IAwsAuthenticator +{ + Task GetDomainOwnerIdAsync(); + Task GetDomainNameAsync(); + Task GetAuthorizationTokenAsync(string domainName, string domainOwner); +} \ No newline at end of file diff --git a/src/AwsServiceAuthenticator.Core/Interfaces/ICommand.cs b/src/AwsServiceAuthenticator.Core/Interfaces/ICommand.cs new file mode 100644 index 0000000..9d1d0cc --- /dev/null +++ b/src/AwsServiceAuthenticator.Core/Interfaces/ICommand.cs @@ -0,0 +1,6 @@ +namespace AwsTokenRefresher.Core.Interfaces; + +public interface ICommand +{ + Task ExecuteAsync(); +} \ No newline at end of file diff --git a/src/AwsServiceAuthenticator.Core/Interfaces/ICommandResolver.cs b/src/AwsServiceAuthenticator.Core/Interfaces/ICommandResolver.cs new file mode 100644 index 0000000..d4ad090 --- /dev/null +++ b/src/AwsServiceAuthenticator.Core/Interfaces/ICommandResolver.cs @@ -0,0 +1,6 @@ +namespace AwsTokenRefresher.Core.Interfaces; + +public interface ICommandResolver +{ + ICommand? ResolveCommand(string commandName); +} \ No newline at end of file diff --git a/src/AwsServiceAuthenticator.Core/Interfaces/ILogger.cs b/src/AwsServiceAuthenticator.Core/Interfaces/ILogger.cs new file mode 100644 index 0000000..f1d92df --- /dev/null +++ b/src/AwsServiceAuthenticator.Core/Interfaces/ILogger.cs @@ -0,0 +1,8 @@ +namespace AwsTokenRefresher.Core.Interfaces; + +public interface ILogger +{ + void LogInformation(string message); + void LogError(string message); + void LogError(string message, Exception exception); +} \ No newline at end of file diff --git a/src/AwsServiceAuthenticator.Core/Interfaces/ISystemRegion.cs b/src/AwsServiceAuthenticator.Core/Interfaces/ISystemRegion.cs new file mode 100644 index 0000000..82550ae --- /dev/null +++ b/src/AwsServiceAuthenticator.Core/Interfaces/ISystemRegion.cs @@ -0,0 +1,6 @@ +namespace AwsTokenRefresher.Core.Interfaces; + +public interface ISystemRegion +{ + public string Region { get; } +} \ No newline at end of file diff --git a/src/AwsServiceAuthenticator.Core/Models/SystemRegion.cs b/src/AwsServiceAuthenticator.Core/Models/SystemRegion.cs new file mode 100644 index 0000000..4674d88 --- /dev/null +++ b/src/AwsServiceAuthenticator.Core/Models/SystemRegion.cs @@ -0,0 +1,13 @@ +using AwsTokenRefresher.Core.Interfaces; + +namespace AwsTokenRefresher.Core.Models; + +public record SystemRegion : ISystemRegion +{ + public string Region { get; init; } + + public SystemRegion(string region) + { + Region = region; + } +} \ No newline at end of file diff --git a/src/AwsServiceAuthenticator.Infrastructure/AwsServiceAuthenticator.Infrastructure.csproj b/src/AwsServiceAuthenticator.Infrastructure/AwsServiceAuthenticator.Infrastructure.csproj new file mode 100644 index 0000000..2443f30 --- /dev/null +++ b/src/AwsServiceAuthenticator.Infrastructure/AwsServiceAuthenticator.Infrastructure.csproj @@ -0,0 +1,24 @@ + + + + net8.0 + enable + enable + AwsTokenRefresher.Infrastructure + + + + + + + + + + + + + + + + + diff --git a/src/AwsServiceAuthenticator.Infrastructure/Services/AwsAuthenticator.cs b/src/AwsServiceAuthenticator.Infrastructure/Services/AwsAuthenticator.cs new file mode 100644 index 0000000..d015141 --- /dev/null +++ b/src/AwsServiceAuthenticator.Infrastructure/Services/AwsAuthenticator.cs @@ -0,0 +1,106 @@ +using Amazon; +using Amazon.CodeArtifact; +using Amazon.CodeArtifact.Model; +using Amazon.Runtime; +using Amazon.SecurityToken; +using Amazon.SecurityToken.Model; +using AwsTokenRefresher.Core.Interfaces; + +namespace AwsTokenRefresher.Infrastructure.Services; + +public class AwsAuthenticator : IAwsAuthenticator +{ + private readonly ILogger _logger; + private readonly string _regionSystemName; + + public AwsAuthenticator(ILogger logger, ISystemRegion systemName) + { + _logger = logger; + _regionSystemName = systemName.Region; + } + public async Task GetDomainOwnerIdAsync() + { + try + { + var config = new AmazonSecurityTokenServiceConfig + { + Timeout = TimeSpan.FromSeconds(30), + RegionEndpoint = RegionEndpoint.GetBySystemName(_regionSystemName) + }; + var stsClient = new AmazonSecurityTokenServiceClient(config); + + _logger.LogInformation("Attempting to get caller identity..."); + var response = await stsClient.GetCallerIdentityAsync(new GetCallerIdentityRequest()); + _logger.LogInformation($"Caller identity retrieved: {response.Account}"); + return response.Account; + } + catch (AmazonServiceException ex) + { + _logger.LogInformation($"Amazon service exception: {ex.Message}"); + _logger.LogInformation($"Stack trace: {ex.StackTrace}"); + throw; + } + catch (Exception ex) + { + _logger.LogInformation($"Exception: {ex.Message}"); + _logger.LogInformation($"Stack trace: {ex.StackTrace}"); + throw; + } + } + + public async Task GetDomainNameAsync() + { + try + { + using var client = new AmazonCodeArtifactClient(RegionEndpoint.GetBySystemName(_regionSystemName)); + _logger.LogInformation("Attempting to list CodeArtifact domains..."); + var response = await client.ListDomainsAsync(new ListDomainsRequest()); + if (response.Domains.Count > 0) + { + _logger.LogInformation($"Domain found: {response.Domains[0].Name}"); + return response.Domains[0].Name; + } + throw new Exception("No CodeArtifact domains found."); + } + catch (AmazonServiceException ex) + { + _logger.LogInformation($"Amazon service exception: {ex.Message}"); + _logger.LogInformation($"Stack trace: {ex.StackTrace}"); + throw; + } + catch (Exception ex) + { + _logger.LogInformation($"Exception: {ex.Message}"); + _logger.LogInformation($"Stack trace: {ex.StackTrace}"); + throw; + } + } + + public async Task GetAuthorizationTokenAsync(string domainName, string domainOwner) + { + try + { + using var client = new AmazonCodeArtifactClient(RegionEndpoint.GetBySystemName(_regionSystemName)); + _logger.LogInformation($"Attempting to get authorization token for domain: {domainName}, owner: {domainOwner}..."); + var response = await client.GetAuthorizationTokenAsync(new GetAuthorizationTokenRequest + { + Domain = domainName, + DomainOwner = domainOwner + }); + _logger.LogInformation("Authorization token retrieved."); + return response.AuthorizationToken; + } + catch (AmazonServiceException ex) + { + _logger.LogInformation($"Amazon service exception: {ex.Message}"); + _logger.LogInformation($"Stack trace: {ex.StackTrace}"); + throw; + } + catch (Exception ex) + { + _logger.LogInformation($"Exception: {ex.Message}"); + _logger.LogInformation($"Stack trace: {ex.StackTrace}"); + throw; + } + } +} \ No newline at end of file diff --git a/src/AwsServiceAuthenticator.Infrastructure/Services/Logger.cs b/src/AwsServiceAuthenticator.Infrastructure/Services/Logger.cs new file mode 100644 index 0000000..a8145e5 --- /dev/null +++ b/src/AwsServiceAuthenticator.Infrastructure/Services/Logger.cs @@ -0,0 +1,29 @@ +using Serilog; +using ILogger = AwsTokenRefresher.Core.Interfaces.ILogger; + +namespace AwsTokenRefresher.Infrastructure.Services; + +public class SerilogLogger : ILogger +{ + private readonly Serilog.ILogger _logger; + + public SerilogLogger(Serilog.ILogger logger) + { + _logger = logger; + } + + public void LogInformation(string message) + { + _logger.Information(message); + } + + public void LogError(string message) + { + _logger.Error(message); + } + + public void LogError(string message, Exception exception) + { + _logger.Error(exception, message); + } +} \ No newline at end of file diff --git a/tests/Integration/AwsServiceAuthenticator.IntegrationTests/AwsServiceAuthenticator.IntegrationTests.csproj b/tests/Integration/AwsServiceAuthenticator.IntegrationTests/AwsServiceAuthenticator.IntegrationTests.csproj new file mode 100644 index 0000000..c2482f6 --- /dev/null +++ b/tests/Integration/AwsServiceAuthenticator.IntegrationTests/AwsServiceAuthenticator.IntegrationTests.csproj @@ -0,0 +1,33 @@ + + + + net8.0 + enable + enable + + false + true + AwsTokenRefresher.Tests + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/Integration/AwsServiceAuthenticator.IntegrationTests/Base/IntegrationTestBase.cs b/tests/Integration/AwsServiceAuthenticator.IntegrationTests/Base/IntegrationTestBase.cs new file mode 100644 index 0000000..90fd317 --- /dev/null +++ b/tests/Integration/AwsServiceAuthenticator.IntegrationTests/Base/IntegrationTestBase.cs @@ -0,0 +1,33 @@ +using AwsTokenRefresher.Commands; +using AwsTokenRefresher.Core.Interfaces; +using AwsTokenRefresher.Infrastructure.Services; +using Microsoft.Extensions.DependencyInjection; +using Moq; + +namespace AwsTokenRefresher.Tests.Base; + +public abstract class IntegrationTestBase +{ + internal static ServiceProvider ServiceProvider { get; private set; } + + [SetUp] + public void SetUp() + { + var serviceCollection = new ServiceCollection(); + var loggerMock = new Mock(); + var systemRegionMock = new Mock(); + systemRegionMock.Setup(s => s.Region).Returns("us-east-1"); + + serviceCollection.AddSingleton(loggerMock.Object); + serviceCollection.AddSingleton(systemRegionMock.Object); + serviceCollection.AddSingleton(); + + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + + ServiceProvider = serviceCollection.BuildServiceProvider(); + } + + [TearDown] + public void TearDown() => ServiceProvider?.Dispose(); +} \ No newline at end of file diff --git a/tests/Integration/AwsServiceAuthenticator.IntegrationTests/EcrAuthCommandTest.cs b/tests/Integration/AwsServiceAuthenticator.IntegrationTests/EcrAuthCommandTest.cs new file mode 100644 index 0000000..5a4a1a0 --- /dev/null +++ b/tests/Integration/AwsServiceAuthenticator.IntegrationTests/EcrAuthCommandTest.cs @@ -0,0 +1,16 @@ +using AwsTokenRefresher.Commands; +using AwsTokenRefresher.Tests.Base; +using Microsoft.Extensions.DependencyInjection; + +namespace AwsTokenRefresher.Tests; + +[TestFixture] +public class EcrAuthIntegrationTestTest : IntegrationTestBase +{ + [Test] + public void EcrAuthCommand_ShouldNotThrow() + { + var command = ServiceProvider.GetRequiredService(); + Assert.DoesNotThrowAsync(async () => await command.ExecuteAsync()); + } +} \ No newline at end of file diff --git a/tests/Integration/AwsServiceAuthenticator.IntegrationTests/NugetAuthCommandTest.cs b/tests/Integration/AwsServiceAuthenticator.IntegrationTests/NugetAuthCommandTest.cs new file mode 100644 index 0000000..5ab8685 --- /dev/null +++ b/tests/Integration/AwsServiceAuthenticator.IntegrationTests/NugetAuthCommandTest.cs @@ -0,0 +1,16 @@ +using AwsTokenRefresher.Commands; +using AwsTokenRefresher.Tests.Base; +using Microsoft.Extensions.DependencyInjection; + +namespace AwsTokenRefresher.Tests; + +[TestFixture] +public class NugetAuthCommandTest : IntegrationTestBase +{ + [Test] + public void NuGetAuthCommand_ShouldNotThrow() + { + var command = ServiceProvider.GetRequiredService(); + Assert.DoesNotThrowAsync(async () => await command.ExecuteAsync()); + } +} \ No newline at end of file