I think all of us DevOps guys hate to see passwords in the github repos. So you know the feeling when I saw multiple passwords in Dockerfile and Nuget configuration 😤. So I get on to the work and started to investigate how can I change this and educate others so we don’t have to use passwords for our builds and deploys in Azure Devops.
While searching for the solution I came accross this github link.
This gave me an idea and solution on how to procceed with my solution.
Let me show you how I have created Azure pipelins for .net and python builds.
First let’s start with .net build.
In the yaml file for azure pipeline most important thing is FEED_ACCESSTOKEN
.
Example yaml:
trigger:
branches:
include:
- develop
- main
variables:
- name: regServiceConnection
value: default
- name: regServiceConnection
value: default
- name: regURL
value: default
stages:
- stage: Build
displayName: 'Build Docker'
jobs:
- job: Build
displayName: 'Build'
pool:
vmImage: 'ubuntu-latest'
steps:
- task: NuGetAuthenticate@1
displayName: 'Authenticate to NuGet'
- task: Docker@2
displayName: Login to Azure Container Registry
inputs:
command: login
containerRegistry: $(regServiceConnection)
- task: DockerInstaller@0
inputs:
dockerVersion: '17.09.0-ce'
- task: Docker@0
inputs:
command: 'build'
Dockerfile: '$(System.DefaultWorkingDirectory)/Dockerfile'
defaultContext: false
context: '$(System.DefaultWorkingDirectory)'
buildArguments: |
FEED_ACCESSTOKEN=$(VSS_NUGET_ACCESSTOKEN)
additionalImageTags: |
$(image_tag)
$(Build.BuildId)
latest
imageName: '$(regURL):$(image_tag)'
- task: Docker@2
displayName: Push an image to container registry
condition: not(eq(variables['Build.Reason'], 'PullRequest'))
inputs:
command: push
repository: $(regRepository)
containerRegistry: $(regServiceConnection)
tags: |
$(image_tag)
$(Build.BuildId)
latest
Main things here are NuGetAuthenticate@1
and passing the build argument FEED_ACCESSTOKEN
.
NuGet Authenticate task uses Project build administrator to authenticate so we can access private Nuget Feeds hosted in Azure DevOps.
Creating Dcokerfile
FROM mcr.microsoft.com/dotnet/aspnet:6.0-focal AS base
WORKDIR /app
FROM mcr.microsoft.com/dotnet/sdk:6.0-focal AS build
ARG FEED_ACCESSTOKEN
WORKDIR /src
RUN dotnet restore --configfile "./NuGet.Config" "myproject.csproj"
COPY . .
WORKDIR "/src/myproject"
RUN dotnet build "myproject.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "myproject.csproj" -c Release -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "myproject.dll"]
Nuget.Config for the .NET
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
# Private repo
</packageSources>
<packageSourceCredentials>
<NugetFeed>
<add key="Username" value="Project Collection Build Service (myproject)" />
<add key="ClearTextPassword" value="%FEED_ACCESSTOKEN%"/>
</NugetFeed>
</packageSourceCredentials>
</configuration>
To complete whole process there is one more step that we need to, and that is to add permissions to Project Collection Build Service(project)
to the Artifacts feed we need to access.
In Dockerfile we are using ARG FEED_ACCESSTOKEN
which is being passed from azure-pipeline to our Dockerfile in build step. This will only stay in the build part and when the image is built it will not exist. The FEED_ACCESSTOKEN exists only for this build, and after the build is done token gets deleted.
I will just leave examples for Python here as a reference:
Dockerfile:
################### Prep stage
FROM python:3.9-bullseye AS prep-stage
#Update os and install runtime dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
libpq-dev
################### Build stage
FROM prep-stage AS build-stage
RUN apt-get install -y --no-install-recommends \
build-essential \
gcc
WORKDIR /home/myproject
RUN python3 -m venv ~/venv
ARG FEED_IDENTITY
ARG FEED_PASSWORD
ARG FEED_URL
ARG PIP_EXTRA_INDEX_URL=https://${FEED_IDENTITY}:${FEED_PASSWORD}@${FEED_URL}
RUN . ~/venv/bin/activate && pip install -r requirements.txt --no-warn-script-location --no-cache-dir
WORKDIR /src/myproject
ENTRYPOINT ["python"]
CMD ["myproject.py"]
Yaml pipeline:
trigger:
- main
- develop
variables:
- name: imageTag
value: '$(Build.SourceBranchName)'
- name: repository
value: 'default'
- name: dockerfile
value: '$(Build.SourcesDirectory)/Dockerfile'
- name: containerRegistry
value: 'default'
- name: dockerRegistryServiceConnection
value: 'default'
stages:
- stage: Build
displayName: Build and publish stage
jobs:
- job: Build
displayName: Build job
pool:
vmImage: 'ubuntu-latest'
steps:
- task: Docker@2
displayName: Build docker image
inputs:
command: build
repository: $(repository)
containerRegistry: $(dockerRegistryServiceConnection)
dockerfile: $(dockerfile)
tags: 'latest'
arguments: |
--tag $(repository)
--build-arg FEED_IDENTITY=$(FEED_IDENTITY)
--build-arg FEED_PASSWORD=$(FEED_PASSWORD)
--build-arg FEED_URL=$(FEED_URL)
- task: trivy@1
displayName: Run Vulnerability Scan
inputs:
docker: false
image: $(repository)
#ignoreUnfixed: true
exitCode: 0
options: --reset
- task: Docker@2
displayName: Publish image to Azure Container Registry
inputs:
command: push
containerRegistry: $(dockerRegistryServiceConnection)
repository: $(repository)
tags: 'latest'
In the end we have moved away from hardcoded passwords and now use one time passwords which are managed by the Azure. I got to admit this way of building applications with one time passwords makes my heart at rest😂.