Skip to content

Commit

Permalink
DevSecOps pipeline with GitHub Actions (#760)
Browse files Browse the repository at this point in the history
Co-authored-by: AleksIvanovSinglet <Aleksandar.Ivanov@singlet.dev>
Co-authored-by: AleksIvanovSinglet <74899441+AleksIvanovSinglet@users.noreply.github.com>
Co-authored-by: Svetoslav Zlatkov <62066501+szlatkow@users.noreply.github.com>
Co-authored-by: Svetoslav Zlatkov <zlatkov@singlet.dev>
Co-authored-by: Aleksandar Ivanov <74899441+aleks-ivanov@users.noreply.github.com>
6 people authored Mar 24, 2021
1 parent 037e739 commit 38dfba1
Showing 43 changed files with 1,138 additions and 5 deletions.
21 changes: 21 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
version: 2
updates:
- package-ecosystem: "github-actions"
# default location of `.github/workflows`
directory: "/"
schedule:
interval: "weekly"

- package-ecosystem: "nuget"
# location of package manifests
directory: "/src/Notepads"
schedule:
interval: "daily"

- package-ecosystem: "nuget"
# location of package manifests
directory: "/src/Notepads.Controls"
schedule:
interval: "daily"

# Built with ❤ by [Pipeline Foundation](https://pipeline.foundation)
73 changes: 73 additions & 0 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: CodeQL Analysis

on:
push:
pull_request:
schedule:
- cron: '0 8 * * *'

jobs:
analyze:
name: codeql-analysis
runs-on: windows-latest
strategy:
matrix:
configuration: [ Production ]
env:
SOLUTION_NAME: src\Notepads.sln
PROJECT_PATH: src\Notepads\Notepads.csproj
CONFIGURATION: ${{ matrix.configuration }}
steps:
- name: Checkout repository
id: checkout_repo
uses: actions/checkout@v2

# Due to the insufficient memory allocated by default, CodeQL sometimes requires more to be manually allocated
- name: Configure Pagefile
id: config_pagefile
uses: al-cheb/configure-pagefile-action@v1.2
with:
minimum-size: 8GB
maximum-size: 32GB
disk-root: "D:"

- name: Setup MSBuild
id: setup_msbuild
uses: microsoft/setup-msbuild@v1

- name: Restore application
id: restore_app
shell: pwsh
run: |
msbuild $env:SOLUTION_NAME `
/t:Restore `
/p:Configuration=$env:CONFIGURATION
- name: Initialize CodeQL
id: init_codeql
uses: github/codeql-action/init@v1
with:
queries: security-and-quality

- name: Build application
id: build_app
shell: pwsh
run: |
msbuild $env:PROJECT_PATH `
/p:Platform=$env:PLATFORM `
/p:Configuration=$env:CONFIGURATION `
/p:UapAppxPackageBuildMode=$env:APPX_PACKAGE_BUILD_MODE `
/p:AppxBundle=$env:APPX_BUNDLE `
/p:AppxPackageSigningEnabled=false `
/p:AppxBundlePlatforms="$env:APPX_BUNDLE_PLATFORMS"
env:
PLATFORM: x64
APPX_PACKAGE_BUILD_MODE: StoreUpload
APPX_BUNDLE: Always
APPX_BUNDLE_PLATFORMS: x64

- name: Perform CodeQL Analysis
id: analyze_codeql
uses: github/codeql-action/analyze@v1

# Built with ❤ by [Pipeline Foundation](https://pipeline.foundation)
58 changes: 58 additions & 0 deletions .github/workflows/csa-bulk-dismissal.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: Code scanning alerts bulk dismissal

on: [workflow_dispatch]

jobs:
dismiss-alerts:
name: Dismiss alerts
runs-on: ubuntu-latest
strategy:
matrix:
ALERT_DESC: ['"Calls to unmanaged code"', '"Unmanaged code"']
env:
# Settings
OWNER: ${{ github.repository_owner }} # verbatim from URL
PROJECT_NAME: ${{ github.event.repository.name }} # verbatim from URL
ACCESS_TOKEN: ${{ secrets.CSA_ACCESS_TOKEN }} # requires security_events read/write permissions
DISMISS_REASON: ${{ secrets.DISMISS_REASON_VAR }} # "false positive", "won't fix" or "used in tests".
ALERTS_PER_PAGE: 100
ALERT_DESCRIPTION: ${{ matrix.ALERT_DESC }}
steps:
- name: Install jq
id: install_jq
uses: r26d/jq-action@master
with:
cmd: jq -n env

- name: Run automation
id: run_automation
shell: bash
run: |
page=1
LIST_OF_ALERTS=$(curl -u $OWNER:$ACCESS_TOKEN -H "Accept: application/vnd.github.v3+json" "https://api.github.com/repos/$OWNER/$PROJECT_NAME/code-scanning/alerts?state=open&page=$page&per_page=$ALERTS_PER_PAGE"| jq .[].number )
while [ -n "$LIST_OF_ALERTS" ]
do
echo -n $LIST_OF_ALERTS" " >> "data.json"
((page=page+1))
LIST_OF_ALERTS=$(curl -u $OWNER:$ACCESS_TOKEN -H "Accept: application/vnd.github.v3+json" "https://api.github.com/repos/$OWNER/$PROJECT_NAME/code-scanning/alerts?state=open&page=$page&per_page=$ALERTS_PER_PAGE"| jq .[].number )
done
LIST_OF_INDEXES=$(cat data.json)
for index in $LIST_OF_INDEXES
do
ALERT_DESC=$(curl -u $OWNER:$ACCESS_TOKEN -H "Accept: application/vnd.github.v3+json" "https://api.github.com/repos/$OWNER/$PROJECT_NAME/code-scanning/alerts/$index" | jq .rule.description)
if [ "$ALERT_DESC" == "$ALERT_DESCRIPTION" ]; then
ALERT_URL="https://api.github.com/repos/$OWNER/$PROJECT_NAME/code-scanning/alerts/$index"
curl -u $OWNER:$ACCESS_TOKEN -X PATCH -H "Accept: application/vnd.github.v3+json" $ALERT_URL -d '{"state":"dismissed","dismissed_reason":"'"$DISMISS_REASON"'"}'
fi
done
rm -f data.json
# Built with ❤ by [Pipeline Foundation](https://pipeline.foundation)
259 changes: 259 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
name: Notepads CI/CD Pipeline

on: [push, pull_request, workflow_dispatch]

jobs:
ci:
runs-on: windows-latest
strategy:
matrix:
configuration: [ Production ]
outputs:
new_version: ${{ steps.get_assembly_version.outputs.version_num }}
new_version_tag: ${{ steps.get_assembly_version.outputs.version_tag }}
latest_tag: ${{ steps.get_latest_tag.outputs.tag }}
is_push_to_master: ${{ steps.step_conditionals_handler.outputs.is_push_to_master }}
env:
SOLUTION_NAME: src\Notepads.sln
CONFIGURATION: ${{ matrix.configuration }}
steps:
- name: Steps' conditionals handler
id: step_conditionals_handler
shell: pwsh
run: |
$IS_PUSH_TO_MASTER = 'false'
$IS_NOT_PR = 'true'
if ( ($env:GITHUB_EVENT_NAME -ceq 'push') -and ($env:GITHUB_REF -ceq 'refs/heads/master') ) {
$IS_PUSH_TO_MASTER = 'true'
}
if ( $env:GITHUB_EVENT_NAME -ceq 'pull_request' ) {
$IS_NOT_PR = 'false'
}
echo "::set-output name=is_push_to_master::$(echo $IS_PUSH_TO_MASTER)"
echo "::set-output name=is_not_pr::$(echo $IS_NOT_PR)"
env:
GITHUB_EVENT_NAME: ${{ github.event_name }}
GITHUB_REF: ${{ github.ref }}
BUILD_CONFIGURATION: ${{ matrix.configuration }}

- if: steps.step_conditionals_handler.outputs.is_not_pr == 'true'
name: Set up JDK 11
id: Setup_JDK
uses: actions/setup-java@v1
with:
java-version: 1.11

- name: Install .NET Core
id: install_dotnet_dependencies
uses: actions/setup-dotnet@v1

- name: Setup MSBuild
id: setup_msbuild
uses: microsoft/setup-msbuild@v1

- name: Checkout repository
id: checkout_repo
uses: actions/checkout@v2
with:
fetch-depth: 50
token: ${{ secrets.GITHUB_TOKEN }}

- if: steps.step_conditionals_handler.outputs.is_push_to_master == 'true'
name: Get assembly version from appxmanifest
id: get_assembly_version
shell: pwsh
run: |
cd src/Notepads/
$xml = [xml](Get-Content Package.appxmanifest)
$ASSEMBLY_VERSION_NUMBER = $xml.Package.Identity | Select -ExpandProperty Version
echo "::set-output name=version_num::$(echo $ASSEMBLY_VERSION_NUMBER)"
echo "::set-output name=version_tag::$(echo v"$ASSEMBLY_VERSION_NUMBER")"
- if: steps.step_conditionals_handler.outputs.is_push_to_master == 'true'
name: Get latest tag
id: get_latest_tag
shell: pwsh
run: |
$LATEST_TAG = git -c 'versionsort.suffix=-' ls-remote --exit-code --refs --sort='version:refname' --tags "https://github.com/$env:GIT_URL.git" '*.*.*' | tail --lines=1 | cut --delimiter='/' --fields=3
echo "::set-output name=tag::$(echo $LATEST_TAG)"
env:
GIT_URL: ${{ github.repository }}

- if: steps.step_conditionals_handler.outputs.is_push_to_master == 'true' && steps.get_assembly_version.outputs.version_tag != steps.get_latest_tag.outputs.tag
name: Add new tag to repo
id: add_new_tag_to_repo
shell: pwsh
run: |
git config --global user.name $env:GIT_USER_NAME
git config --global user.email $env:GIT_USER_EMAIL
git tag -a -m "$env:NEW_VERSION_TAG" $env:NEW_VERSION_TAG
git push --follow-tags
env:
GIT_USER_NAME: ${{ secrets.GIT_USER_NAME }}
GIT_USER_EMAIL: ${{ secrets.GIT_USER_EMAIL }}
NEW_VERSION_TAG: ${{ steps.get_assembly_version.outputs.version_tag }}

- if: steps.step_conditionals_handler.outputs.is_not_pr == 'true'
name: Cache SonarCloud packages
id: cache_sonar_packages
uses: actions/cache@v2
with:
path: ~\sonar\cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar

- if: steps.step_conditionals_handler.outputs.is_not_pr == 'true'
name: Cache SonarCloud scanner
id: cache_sonar_scanner
uses: actions/cache@v2
with:
path: .\.sonar\scanner
key: ${{ runner.os }}-sonar-scanner
restore-keys: ${{ runner.os }}-sonar-scanner

- if: steps.step_conditionals_handler.outputs.is_not_pr == 'true' && steps.cache_sonar_scanner.outputs.cache-hit != 'true'
name: Install SonarCloud scanner
id: install_sonar_scanner
shell: pwsh
run: |
New-Item -Path .\.sonar\scanner -ItemType Directory
dotnet tool update dotnet-sonarscanner --tool-path .\.sonar\scanner
- if: steps.step_conditionals_handler.outputs.is_not_pr == 'true'
name: Lowercase string generator
id: lowercase_string_gen
shell: pwsh
run: |
$LOWERCASE_OWNER = "${{ github.repository_owner }}".ToLower()
echo "::set-output name=owner_name::$LOWERCASE_OWNER"
- if: steps.step_conditionals_handler.outputs.is_not_pr == 'true'
name: Initialize SonarCloud scanner
id: init_sonar_scanner
shell: pwsh
run: |
.\.sonar\scanner\dotnet-sonarscanner begin `
/k:"${{ github.repository_owner }}_${{ github.event.repository.name }}" `
/o:"${{ steps.lowercase_string_gen.outputs.owner_name }}" `
/d:sonar.login="$env:SONAR_TOKEN" `
/d:sonar.host.url="https://sonarcloud.io"
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

- if: steps.step_conditionals_handler.outputs.is_push_to_master == 'true'
name: Create PFX certificate for AppxBundle
id: create_pfx_cert
shell: pwsh
run: |
$BASE64_STR = $env:BASE64_STR
$TARGET_FILE = "$env:DEFAULT_DIR\cert.pfx"
$FROM_BASE64_STR = [Convert]::FromBase64String($BASE64_STR)
[IO.File]::WriteAllBytes($TARGET_FILE, $FROM_BASE64_STR)
env:
BASE64_STR: ${{ secrets.PACKAGE_CERTIFICATE_BASE64 }}
DEFAULT_DIR: ${{ github.workspace }}

- name: Restore the application
id: restore_application
shell: pwsh
run: |
msbuild $env:SOLUTION_NAME `
/t:Restore `
/p:Configuration=$env:CONFIGURATION
- name: Build and generate bundles
id: build_app
shell: pwsh
run: |
msbuild $env:SOLUTION_NAME `
/p:Platform=$env:PLATFORM `
/p:Configuration=$env:CONFIGURATION `
/p:UapAppxPackageBuildMode=$env:UAP_APPX_PACKAGE_BUILD_MODE `
/p:AppxBundle=$env:APPX_BUNDLE `
/p:AppxPackageSigningEnabled=$env:APPX_PACKAGE_SIGNING_ENABLED `
/p:AppxBundlePlatforms=$env:APPX_BUNDLE_PLATFORMS `
/p:AppxPackageDir=$env:ARTIFACTS_DIR `
/p:PackageCertificateKeyFile=$env:PACKAGE_CERTIFICATE_KEYFILE `
/p:PackageCertificatePassword=$env:PACKAGE_CERTIFICATE_PASSWORD `
/p:AppCenterSecret=$env:APP_CENTER_SECRET
env:
PLATFORM: x64
UAP_APPX_PACKAGE_BUILD_MODE: StoreUpload
APPX_BUNDLE: Always
APPX_PACKAGE_SIGNING_ENABLED: ${{ steps.step_conditionals_handler.outputs.is_push_to_master }}
APPX_BUNDLE_PLATFORMS: x86|x64|ARM64
ARTIFACTS_DIR: ${{ github.workspace }}\Artifacts
PACKAGE_CERTIFICATE_KEYFILE: ${{ github.workspace }}\cert.pfx
PACKAGE_CERTIFICATE_PASSWORD: ${{ secrets.PACKAGE_CERTIFICATE_PWD }}
APP_CENTER_SECRET: ${{ secrets.APP_CENTER_SECRET }}

- if: steps.step_conditionals_handler.outputs.is_not_pr == 'true'
name: Send SonarCloud results
id: send_sonar_results
shell: pwsh
run: |
.\.sonar\scanner\dotnet-sonarscanner end `
/d:sonar.login="${{ secrets.SONAR_TOKEN }}"
env:
GITHUB_TOKEN: ${{ secrets.SONAR_GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

- if: steps.step_conditionals_handler.outputs.is_push_to_master == 'true'
name: Upload build artifacts
id: upload_artifacts
uses: actions/upload-artifact@v1
with:
name: Build artifacts
path: Artifacts/

cd:
# "This job will execute when the workflow is triggered on a 'push event', the target branch is 'master' and the commit is intended to be a release."
if: needs.ci.outputs.is_push_to_master == 'true' && needs.ci.outputs.new_version_tag != needs.ci.outputs.latest_tag
needs: ci
runs-on: windows-latest
env:
NEW_VERSION: ${{ needs.ci.outputs.new_version }}
NEW_TAG: ${{ needs.ci.outputs.new_version_tag }}
steps:
- name: Checkout repository
id: checkout_repo
uses: actions/checkout@v2

- name: Download and extract MSIX package
id: dl_package_artifact
uses: actions/download-artifact@v2
with:
name: Build artifacts
path: Artifacts/

- name: Create and publish release
id: create_release
uses: actions/create-release@v1
with:
tag_name: ${{ env.NEW_TAG }}
release_name: Notepads ${{ env.NEW_TAG }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Upload msixbundle as release asset
id: upload_notepads_zip
uses: actions/upload-release-asset@v1
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: Artifacts/Notepads_${{ env.NEW_VERSION }}_Production_Test/Notepads_${{ env.NEW_VERSION }}_x86_x64_ARM64_Production.msixbundle
asset_name: Notepads_${{ env.NEW_VERSION }}_x86_x64_ARM64.msixbundle
asset_content_type: application/zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Publish to Windows Store
id: publish_to_store
uses: isaacrlevin/windows-store-action@1.0
with:
tenant-id: ${{ secrets.AZURE_AD_TENANT_ID }}
client-id: ${{ secrets.AZURE_AD_APPLICATION_CLIENT_ID }}
client-secret: ${{ secrets.AZURE_AD_APPLICATION_SECRET }}
app-id: ${{ secrets.STORE_APP_ID }}
package-path: "${{ github.workspace }}/Artifacts/"

# Built with ❤ by [Pipeline Foundation](https://pipeline.foundation)
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -203,7 +203,6 @@ rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx

334 changes: 334 additions & 0 deletions CI-CD_DOCUMENTATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,334 @@
# Notepads CI/CD documentation

- after merging the PR, the first run of the "Notepads CI/CD Pipeline" workflow will not complete successfully, because it requires specific setup explained in this documentation. The two other workflows "CodeQL Analysis" and "Build", should complete successfully.

## 1. Set up SonarCloud

### SonarCloud is a cloud-based code quality and security service

#### Create SonarCloud project

- Go to https://sonarcloud.io/

- Click the "Log in" button and create a new account or connect with GitHub account (recommended)

- At the top right corner click the "+" sign

- From the dropdown select "Create new Organization"

- Click the "Choose an organization on GitHub" button

- Select an account for the organization setup

- On Repository Access select "Only select repositories" and select the project and click the "Save" button

- On the "Create organization page" don't change the Key and click "Continue"

- Select the Free plan then click the "Create Organization" button to finalize the creation of the Organization

#### Configure SonarCloud project

- At the top right corner click the "+" sign and select "Analyze new project"

- Select the project and click the "Set Up" button in the box on the right

- Under "Choose your analysis method" click "With GitHub Actions" and **keep the following page open**

- [Create a new PAT with **repo_deployment** and **read:packages** permissions](#7-how-to-create-a-pat) and copy the value of the generated token

- In the project's GitHub repository, go to the **Settings** tab -> Secrets

- Click on **New Repository secret** and create a new secret with the name **SONAR_GITHUB_TOKEN** and the token you just copied as the value

- Create another secret with the two values from the SonarCloud page you kept open, which you can close after completing this step

![SonarCloud_1](ScreenShots/CI-CD_DOCUMENTATION/SonarCloud_1.png)

- [Run the "Notepads CI/CD Pipeline" workflow manually](#2-run-workflow-manually)

#### Set Quality Gate

- After the "Notepads CI/CD Pipeline" workflow has executed successfully, go to https://sonarcloud.io/projects and click on the project

- In the alert bar above the results, click the "Set new code definition" button and select "Previous version" (notice the "New Code definition has been updated" alert at the top)

- The Quality Gate will become active as soon as the next SonarCloud scan completes successfully

<br>

## 2. Run workflow manually

Once you've set up all the steps above correctly, you should be able to successfully complete a manual execution of the "Notepads CI/CD Pipeline" workflow.

1. Go to the project's GitHub repository and click on the **Actions** tab

2. From the "Workflows" list on the left, click on "Notepads CI/CD Pipeline"

3. On the right, next to the "This workflow has a workflow_dispatch event trigger" label, click on the "Run workflow" dropdown, make sure the default branch is selected (if not manually changed, should be main or master) in the "Use workflow from" dropdown and click the "Run workflow" button

![Actions_workflow_dispatch](ScreenShots/CI-CD_DOCUMENTATION/Actions_workflow_dispatch.png)

4. Once the workflow run has completed successfully, move on to the next step of the documentation

NOTE: **screenshots are only exemplary**

<br>

## 3. Set up Dependabot

Dependabot is a GitHub native security tool that goes through the dependencies in the project and creates alerts, and PRs with updates when a new and/or non-vulnerable version is found.

- for PRs with version updates, this pipeline comes pre-configured for all current dependency sources in the project, so at "Insights" tab -> "Dependency graph" -> "Dependabot", you should be able to see all tracked sources of dependencies, when they have been checked last and view a full log of the last check

![Dependabot_tab](ScreenShots/CI-CD_DOCUMENTATION/Dependabot_tab.png)

![Dependabot_log_page](ScreenShots/CI-CD_DOCUMENTATION/Dependabot_log_page.png)

### Set up security alerts and updates

##### - GitHub, through Dependabot, also natively offers a security check for vulnerable dependencies

1. Go to "Settings" tab of the repo

2. Go to "Security & analysis" section

3. Click "Enable" for both "Dependabot alerts" and "Dependabot security updates"

- By enabling "Dependabot alerts", you would be notified for any vulnerable dependencies in the project. At "Security" tab -> "Dependabot alerts", you can manage all alerts. By clicking on an alert, you would be able to see a detailed explanation of the vulnerability and a viable solution.

![Dependabot_alerts_page](ScreenShots/CI-CD_DOCUMENTATION/Dependabot_alerts_page.png)

![Dependabot_alert_page](ScreenShots/CI-CD_DOCUMENTATION/Dependabot_alert_page.png)

- By enabling "Dependabot security updates", you authorize Dependabot to create PRs specifically for **security updates**

![Dependabot_PRs](ScreenShots/CI-CD_DOCUMENTATION/Dependabot_PRs.png)

### Set up Dependency graph

##### - The "Dependency graph" option should be enabled by default for all public repos, but in case it isn't:

1. Go to "Settings" tab of the repo

2. Go to "Security&Analysis" section

3. Click "Enable" for the "Dependency graph" option

- this option enables the "Insights" tab -> "Dependency graph" section -> "Dependencies" tab, in which all the dependencies for the project are listed, under the different manifests they are included in

![Dependabot_dependency_graph](ScreenShots/CI-CD_DOCUMENTATION/Dependabot_dependency_graph.png)

NOTE: **screenshots are only exemplary**

<br>

## 4. CodeQL

CodeQL is GitHub's own industry-leading semantic code analysis engine. CodeQL requires no setup, because it comes fully pre-configured by us.

To activate it and see its results, only a push commit or a merge of a PR to the default branch of the repository, is required.

We've also configured CodeQL to run on schedule, so every day at 8:00AM UTC, it automatically scans the code.

- you can see the results here at **Security** tab -> **Code scanning alerts** -> **CodeQL**:

![CodeQL_results](ScreenShots/CI-CD_DOCUMENTATION/CodeQL_results.png)

- on the page of each result, you can see an explanation of what the problem is and also one or more solutions:

![CodeQL_alert_page](ScreenShots/CI-CD_DOCUMENTATION/CodeQL_alert_page.png)

### Code scanning alerts bulk dismissal tool

##### - currently, GitHub allows for only 25 code scanning alerts to be dismissed at a time. Sometimes, you might have hundreds you would like to dismiss, so you will have to click many times and wait for a long time to dismiss them. Via the "csa-bulk-dismissal.yml", you would be able to that with one click.

NOTE: This tool executes manual **only**. It won't execute on any other GitHub event like push commit, PR creation etc.

#### 1. Setup

1. In the repository, go to the **Settings** tab -> **Secrets**

![CSA_secrets](ScreenShots/CI-CD_DOCUMENTATION/CSA_secrets.png)

2. Add the following secrets with the name and the corresponding value, by at the upper right of the section, clicking on the **New repository secret** button:

![CSA_new_secret](ScreenShots/CI-CD_DOCUMENTATION/CSA_new_secret.png)

![CSA_secret_add](ScreenShots/CI-CD_DOCUMENTATION/CSA_secret_add.png)

- CSA_ACCESS_TOKEN - [create a PAT with "security_events" permission only](#7-how-to-create-a-pat).

- DISMISS_REASON_VAR - this secret refers to the reason why you dismissed the code scanning alert. Use the appropriate one as the value of this secret, out of the three available options: **false positive**, **won't fix** or **used in tests**.

#### 2. Execution

1. In your repo, click on the Actions tab and on the left, in the Workflows list, click on the "Code scanning alerts bulk dismissal"

![CSA_execute_1](ScreenShots/CI-CD_DOCUMENTATION/CSA_execute_1.png)

2. On the right, click on the "Run workflow" dropdown. Under "Use workflow from" choose your default branch (usually main/master) and click on the **Run workflow** button

![CSA_execute_2](ScreenShots/CI-CD_DOCUMENTATION/CSA_execute_2.png)

3. If everything was set up currently in the "Setup" phase, the "Code scanning alerts bulk dismissal" workflow is going to be executed successfully, which after some time, would result in **all** previously open code scanning alerts, with a certain description be dismissed

![CSA_execute_3](ScreenShots/CI-CD_DOCUMENTATION/CSA_execute_3.png)

![CSA_execute_4](ScreenShots/CI-CD_DOCUMENTATION/CSA_execute_4.png)

![CSA_execute_5](ScreenShots/CI-CD_DOCUMENTATION/CSA_execute_5.png)

NOTE: "closed" refers to "dismissed" alerts

#### 3. Customization

The "ALERT_DESC" strategy matrix in the pipeline, allows for more precise filtering of alerts to bulk dismiss. It uses the description of the alert to determine if it has to be dismissed or not. We've added the following alert descriptions by default:

- "Calls to unmanaged code"
- "Unmanaged code"

To add more descriptions, follow these steps:

1. In your source code, open ".github/workflows/csa-bulk-dismissal.yml"

2. On line 11, notice "ALERT_DESC: ['"Calls to unmanaged code"', '"Unmanaged code"']". This is the array of descriptions that the CSABD (Code scanning alerts bulk dismissal) tool uses to filter through the alerts:

![CSA_custom_1](ScreenShots/CI-CD_DOCUMENTATION/CSA_custom_1.png)

3. To add more descriptions use comma separation, followed by a single space and the description enclosed in double quotes, then enclosed in single quotes:

![CSA_custom_2](ScreenShots/CI-CD_DOCUMENTATION/CSA_custom_2.png)

<br>

## 5. Automated GitHub release

Automatically bumps up the GitHub tag in the repo, creates a GitHub release with the new tag and attaches the msixbundle to it

#### Setup

Add the following secrets by going to the repo **Settings** tab -> **Secrets**:

1. **GIT_USER_NAME**

- used to add the identity required for creating a tag
- copy and paste your GitHub username as the value of the secret

2. **GIT_USER_EMAIL**

- used to add the identity required for creating a tag
- copy and paste your primary GitHub email as the value of the secret

3. **PACKAGE_CERTIFICATE_BASE64**

- used to dynamically create the PFX file required for the signing of the **msixbundle**
- use the following PowerShell code locally to turn your PFX file into Base64:

```
# read from PFX as binary
$PFX_FILE = [IO.File]::ReadAllBytes('absolute_path_to_PFX')
# convert to Base64 and write in txt
[System.Convert]::ToBase64String($PFX_FILE) | Out-File 'absolute_path\cert.txt'
```

- copy the contents of the **cert.txt** and paste as the value of the secret

4. **PACKAGE_CERTIFICATE_PWD**

- used in the build of the project to authenticate the PFX
- copy and paste the password of your PFX as the value of this secret

NOTE:

- none of those values are visible in the logs of the pipeline, nor are available to anyone outside of the original repository e.g. forks, anonymous clones etc.
- the dynamically created PFX file lives only for the duration of the pipeline execution

#### Execution

Follow these steps to trigger the automated GitHub release process:

1. Bump up the **Package.Identity.Version** in the **Package.appxmanifest**

2. Make a push commit to your master branch or if you've done the previous change in a feature branch, the merge of the PR to the master branch would act as the push commit

If the setup was done correctly and there are no errors in the pipeline, when the pipeline successfully completes, there should be a new, properly tagged GitHub release with the msixbundle attached to it.

NOTE:

- the tag itself is used as the required description of the newly created tag, which appears here:

![Release_1](ScreenShots/CI-CD_DOCUMENTATION/Release_1.png)

- it is replaced by the release description

![Release_2](ScreenShots/CI-CD_DOCUMENTATION/Release_2.png)

<br>

## 6. Setup automated publishing to the Windows Store

#### - for the automation to work, at least one submission needs to be already created manually

- [Create an Azure AD tenant](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-create-new-tenant) or use an existing one

- Associate your [Microsoft Partner Center with the Azure AD tenant](https://docs.microsoft.com/en-us/windows/uwp/publish/associate-azure-ad-with-partner-center)

- [Create a new app registration](https://docs.microsoft.com/en-us/azure/devops/pipelines/library/service-endpoints?view=azure-devops&tabs=yaml) or use an existing one from the list in your **portal.azure.com** -> **Azure Active Directory** -> **App registrations** section

- Add the [Azure AD application to the Microsoft Partner Center](https://docs.microsoft.com/en-us/partner-center/service-principal) and give it "Manager" permissions

- In the project's GitHub repo, create the following secrets:

1. **AZURE_AD_TENANT_ID** and **AZURE_AD_APPLICATION_CLIENT_ID**

- copy and paste the values shown in the screenshot below to the appropriate secret:

![Publish_to_store_1](ScreenShots/CI-CD_DOCUMENTATION/Publish_to_store_1.png)

Note: screenshot is taken from **portal.azure.com** -> **Azure AD** -> **App registrations** -> **app-name** page

2. **AZURE_AD_APPLICATION_SECRET**

- copy and paste the value you get on the page following from **Account settings** -> **User management** -> **Azure AD applications** -> click on the added application:

![Publish_to_store_2](ScreenShots/CI-CD_DOCUMENTATION/Publish_to_store_2.png)

3. **STORE_APP_ID**

- copy and paste the highlighted code as the value of this secret:

![Publish_to_store_3](ScreenShots/CI-CD_DOCUMENTATION/Publish_to_store_3.png)

- If everything was setup correctly, on your next push commit to the `master` branch with a new `Identity.Version` in the `Package.appxmanifest`, a new submission in the Microsoft Partner Center with the new `*.msixupload` package should appear and be automatically submitted if all verifications pass

<br>

## 7. How to create a PAT

- In a new tab open GitHub, at the top right corner, click on your profile picture and click on **Settings** from the dropdown.

![CSA_new_pat_1](ScreenShots/CI-CD_DOCUMENTATION/CSA_new_pat_1.png)

- Go to Developer Settings -> Personal access tokens.

![CSA_new_pat_2](ScreenShots/CI-CD_DOCUMENTATION/CSA_new_pat_2.png)

![CSA_new_pat_3](ScreenShots/CI-CD_DOCUMENTATION/CSA_new_pat_3.png)

- Click the **Generate new token** button and enter password if prompted.

![CSA_new_pat_4](ScreenShots/CI-CD_DOCUMENTATION/CSA_new_pat_4.png)

- Name the token, from the permissions list choose the ones needed and at the bottom click on the **Generate token** button.

![CSA_new_pat_5](ScreenShots/CI-CD_DOCUMENTATION/CSA_new_pat_5.png)

- Copy the token value and paste it wherever its needed

![CSA_new_pat_6](ScreenShots/CI-CD_DOCUMENTATION/CSA_new_pat_6.png)

NOTE: once you close or refresh the page, you won't be able to copy the value of the PAT again!

#

Built with ❤ by [Pipeline Foundation](https://pipeline.foundation)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ScreenShots/CI-CD_DOCUMENTATION/CSA_custom_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ScreenShots/CI-CD_DOCUMENTATION/CSA_custom_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ScreenShots/CI-CD_DOCUMENTATION/CSA_execute_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ScreenShots/CI-CD_DOCUMENTATION/CSA_execute_3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ScreenShots/CI-CD_DOCUMENTATION/CSA_execute_4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ScreenShots/CI-CD_DOCUMENTATION/CSA_execute_5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ScreenShots/CI-CD_DOCUMENTATION/CSA_new_pat_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ScreenShots/CI-CD_DOCUMENTATION/CSA_new_pat_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ScreenShots/CI-CD_DOCUMENTATION/CSA_new_pat_3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ScreenShots/CI-CD_DOCUMENTATION/CSA_new_pat_4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ScreenShots/CI-CD_DOCUMENTATION/CSA_new_pat_5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ScreenShots/CI-CD_DOCUMENTATION/CSA_new_pat_6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ScreenShots/CI-CD_DOCUMENTATION/CSA_secrets.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ScreenShots/CI-CD_DOCUMENTATION/CSA_url_owner.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ScreenShots/CI-CD_DOCUMENTATION/Release_1.png
Binary file added ScreenShots/CI-CD_DOCUMENTATION/Release_2.png
10 changes: 10 additions & 0 deletions src/Notepads/Notepads.csproj
Original file line number Diff line number Diff line change
@@ -455,6 +455,16 @@
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets" />
<Import Project="Package.targets" />
<Target Name="AssignAppCenterSecret" BeforeTargets="BeforeBuild" Condition=" $(AppCenterSecret) != '' ">
<PropertyGroup>
<InputFile>App.xaml.cs</InputFile>
</PropertyGroup>
<WriteLinesToFile
File="$(InputFile)"
Lines="$([System.IO.File]::ReadAllText($(InputFile)).Replace('AppCenterSecret = null','AppCenterSecret = &quot;$(AppCenterSecret)&quot;'))"
Overwrite="true"
Encoding="Unicode"/>
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
379 changes: 379 additions & 0 deletions src/Notepads/Package.StoreAssociation.xml

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/Notepads/Package.appxmanifest
Original file line number Diff line number Diff line change
@@ -11,8 +11,8 @@
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="mp uap uap5 uap10 desktop4 iot2 rescap">

<Identity Name="Notepads" Publisher="CN=jackil" Version="1.4.2.0" />
<mp:PhoneIdentity PhoneProductId="dde28e6c-0f89-4885-83bc-51d9580080a1" PhonePublisherId="00000000-0000-0000-0000-000000000000" />
<Identity Name="19282JackieLiu.Notepads-Beta" Publisher="CN=40E66D07-5A3A-4954-9CA3-A1EB15ED0804" Version="1.4.3.0" />
<mp:PhoneIdentity PhoneProductId="df234254-de03-48f0-80a0-11b5082ec740" PhonePublisherId="00000000-0000-0000-0000-000000000000" />

<Properties>
<DisplayName>Notepads App</DisplayName>
4 changes: 2 additions & 2 deletions src/Notepads/Package.targets
Original file line number Diff line number Diff line change
@@ -56,7 +56,7 @@
</Code>
</Task>
</UsingTask>

<Target Name="UpdateAppxManifest" BeforeTargets="BeforeBuild" Condition="'$(Configuration)' != 'Production'">
<UpdateNotepadsPackageManifest ManifestPath="%(AppxManifest.FullPath)"
OutDir="$(ProjectDir)$(BaseIntermediateOutputPath)"
@@ -68,7 +68,7 @@
<AppxManifest Include="$(GeneratedAppxManifest)" />
</ItemGroup>
</Target>

<!-- Manually set resource qualifiers after removing `Language` qualifier from Resource Package Qualifiers-->
<!-- https://docs.microsoft.com/en-us/windows/uwp/app-resources/build-resources-into-app-package -->
<Target Name="SetAppxDefaultResourceQualifiers" BeforeTargets="BeforeBuild">

0 comments on commit 38dfba1

Please sign in to comment.