Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add rule templates sync workflow #2012

Merged
merged 2 commits into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions .github/workflows/sync-rule-templates.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: Sync CIS Rule Templates

on:
push:
branches:
- main
paths:
- "security-policies/bundle/compliance/**/rules/**/data.yaml"

env:
GITHUB_TOKEN: ${{ secrets.CLOUDSEC_MACHINE_TOKEN }}

jobs:
Sync-Templates:
name: Sync CIS Rule Templates
runs-on: ubuntu-22.04
timeout-minutes: 60
steps:
- name: Checkout Integrations repo
uses: actions/checkout@v4
with:
token: ${{ secrets.CLOUDSEC_MACHINE_TOKEN }}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need a token for checkout?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because we need access to push/open PR to the integrations too

repository: elastic/integrations
path: integrations

- name: Checkout Cloudbeat repo
uses: actions/checkout@v4
with:
token: ${{ secrets.CLOUDSEC_MACHINE_TOKEN }}
path: cloudbeat

- name: Init Hermit
working-directory: cloudbeat
run: ./bin/hermit env -r >> $GITHUB_ENV

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.9"

- name: Install Poetry
working-directory: cloudbeat
run: |
curl -sSL https://install.python-poetry.org | python3 -
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We usually install it using a package manager like pip/pipx in other workflows, any reason to install it directly?

Suggested change
curl -sSL https://install.python-poetry.org | python3 -
pipx install poetry

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like most workflows install in via curl. hardly an issue IMO

poetry --version

- name: Install dependencies
working-directory: cloudbeat/security-policies
run: poetry install

- name: Sync CIS Rules with integrations repo
working-directory: cloudbeat
run: scripts/sync_rule_templates.sh
69 changes: 69 additions & 0 deletions scripts/common.sh
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added new utility functions to bump an integration version. the version bump dispatch workflow also does this and later on it will use these functions

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this file is currently excluded from shellcheck and introduces 2 new errors:

In scripts/common.sh line 93:
    local second_version="$(echo "${major2}.${minor2}.x" | xargs)"
          ^------------^ SC2155 (warning): Declare and assign separately to avoid masking return values.


In scripts/common.sh line 124:
        sed -i '' -e '3i\'$'\n'"$next_entry" "$changelog_path"
                        ^-- SC1003 (info): Want to escape a single quote? echo 'This is how it'\''s done'.

it works without resolving them and they don't seem critical. someday later on we'll remove this file from the excluded paths and fix all errors.

Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,72 @@ restart_agents() {
exec_pod $POD "elastic-agent restart"
done
}

bump_preview_version() {
local current_version="${1:?Missing current version}"
preview_number="${current_version##*-preview}"
((next_preview_number = 10#${preview_number} + 1))
echo "${current_version%-*}-preview$(printf "%02d" $next_preview_number)"
}

bump_minor_version() {
local version="${1:?Missing version}"
IFS='.' read -r major minor _ <<<"$1"
((minor++))
echo "$major.$minor.0"
}

get_integration_version() {
local changelog_path="${1:?Missing changelog.yml path}"
current_version=$(yq '.[0].version' "$changelog_path" | tr -d '"')
echo "$current_version"
}

get_new_integration_version_map_entry() {
local latest_version="${1:?Missing latest versions entry}"
IFS='-' read -r group1 group2 <<<"$latest_version"
IFS='.' read -r major1 minor1 _ <<<"$group1"
IFS='.' read -r major2 minor2 _ <<<"$group2"
((minor1++))
((minor2++))
local first_version="${major1}.${minor1}.x"
local second_version="$(echo "${major2}.${minor2}.x" | xargs)"
echo "$first_version - $second_version"
}

# bumps existing preview version: 1.0.0-preview01 -> 1.0.0-preview02, or
# creates a new preview version: 1.0.0 -> 1.1.0-preview01, and
# updates the manifest and changelog files
bump_integration_version() {
Comment on lines +97 to +100
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a PR to an integration must include a changelog and manifest update. depending on when the PR is made, the current integration version may be one of the following:

  1. some preview version, like 1.0.0-preview01, in which case the preview suffix will be incremented. see example PR for actual changes made
  2. some version, like 1.0.0, in which case the changes will result in this example diff:
+++ b/packages/cloud_security_posture/changelog.yml
@@ -1,5 +1,6 @@
 # newer versions go on top
 # version map:
+# 1.10.x - 8.15.x
 # 1.9.x - 8.14.x
 # 1.8.x - 8.13.x
 # 1.7.x - 8.12.x
@@ -8,6 +9,11 @@
 # 1.4.x - 8.9.x
 # 1.3.x - 8.8.x
 # 1.2.x - 8.7.x
+- version: 1.10.0-preview01
+  changes:
+    - description: Bump version
+      type: enhancement
+      link: https://github.com/elastic/integrations/pull/9328
 - version: "1.9.0"
   changes:
     - description: Convert fields to secrets
diff --git a/packages/cloud_security_posture/manifest.yml b/packages/cloud_security_posture/manifest.yml
index bda29ce3e..aaa4fcca8 100644
--- a/packages/cloud_security_posture/manifest.yml
+++ b/packages/cloud_security_posture/manifest.yml
@@ -1,7 +1,7 @@
 format_version: 3.0.0
 name: cloud_security_posture
 title: "Security Posture Management"
-version: "1.9.0"
+version: "1.10.0-preview01"
 source:
   license: "Elastic-2.0"
 description: "Identify & remediate configuration risks in your Cloud infrastructure"
@@ -11,7 +11,7 @@ categories:
   - cloudsecurity_cdr
 conditions:
   kibana:
-    version: "^8.14.0"
+    version: "^8.15.0"
   elastic:
     subscription: basic
     capabilities:

changelog_path="${1:?Missing changelog.yml path}"
manifest_path="${2:?Missing manifest.yml path}"
pr_url="${3:?Missing PR URL}"
changelog_description="${4:-Bump version}"
# exports required for yq's env()
export changelog_description
export pr_url
version="$(get_integration_version "$changelog_path")"
if [[ $version == *"preview"* ]]; then
next_version=$(bump_preview_version "$version")
export next_version
# update current version and add new changes entry
yq -i ".[0].version = \"$next_version\"" "$changelog_path"
yq -i '.[0].changes += [{"description": env(changelog_description), "type": "enhancement", "link": env(pr_url) }]' "$changelog_path"
else
next_version="$(bump_minor_version "$version")-preview01"
export next_version
# add new version + changes entry
yq -i '. = [{"version": env(next_version), "changes": [{"description": env(changelog_description), "type": "enhancement", "link": env(pr_url) }]}] + .' "$changelog_path"

# add new version map for integration - kibana
latest_entry="$(sed -n '3p' "$changelog_path")"
next_entry=$(get_new_integration_version_map_entry "$latest_entry")
sed -i '' -e '3i\'$'\n'"$next_entry" "$changelog_path"

# update manifest with new kibana version
IFS='-' read -r _ next_kibana_version <<<"$next_entry"
IFS='.' read -r major minor _ _ <<<"$(echo "$next_kibana_version" | xargs)"
yq -i ".conditions.kibana.version = \"^$major.$minor.0\"" "$manifest_path"
fi
yq -i ".version = \"$next_version\"" "$manifest_path"
}
80 changes: 80 additions & 0 deletions scripts/sync_rule_templates.sh
Copy link
Collaborator Author

@orouz orouz Mar 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this script does the following:

  1. checkout a new branch from main or the existing branch, with hard reset to main
    • reset is done to avoid conflicts (in manifest/changelog), or outdated branch if a previous PR was closed and not merged, for some reason
    • checking out existing branch instead of deleting to avoid closing an existing PR
  2. generate the rule templates from cloudbeat's main
    • this means that if for example, PR-1 merged a rule and triggered a workflow to open a PR to integrations, then PR-2 was merged to cloudbeat's main with another rule, the generation will include both rules
  3. commit new rule templates (PR will always have only 2 commits: adding templates and bumping version)
  4. if a PR is not opened, open it and assign labels.
  5. bump integration version
  6. edit the PR body with a nice markdown table with links to added rule templates

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gh api is used instead of gh pr because the latter is buggy

Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#!/bin/bash
set -euo pipefail

source scripts/common.sh

git config --global user.email "[email protected]"
git config --global user.name "Cloud Security Machine"

branch_name="sync-cis-rule-templates"
repo="repos/elastic/integrations"
pr_number=$(gh api $repo/pulls -q ".[] | select(.head.ref == \"$branch_name\" and .state == \"open\") | .number")
templates_path="packages/cloud_security_posture/kibana/csp_rule_template"
manifest_path="packages/cloud_security_posture/manifest.yml"
changelog_path="packages/cloud_security_posture/changelog.yml"

# get new or existing sync-cis-rule-templates branch
cd ../integrations
if git fetch origin main "$branch_name" &>/dev/null; then
git checkout "$branch_name"
git reset origin/main --hard # reset to main, avoids conflicts
else
git checkout -b "$branch_name" origin/main
fi

# generate the rule templates
cd ../cloudbeat
poetry run -C security-policies python security-policies/dev/generate_rule_templates.py

# commit and push the changes
cd ../integrations
git add "$templates_path"
git commit -m "Sync CIS rule templates"
git push origin "$branch_name" -f

# create a PR if it doesn't exist and assign labels
if [[ -z "$pr_number" ]]; then
pr=$(gh api \
--method POST \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
/$repo/pulls \
-f title='[Cloud Security] Sync CIS rule templates' \
-f body='' \
-f head="$branch_name" \
-f base='main')

pr_number=$(echo "$pr" | jq -r '.html_url' | awk -F'/' '{print $NF}')

gh api \
--method POST \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"/$repo/issues/$pr_number/labels" \
-f "labels[]=Team:Cloud Security" -f "labels[]=enhancement"
fi

pr_url=$(gh api $repo/pulls -q ".[] | select(.head.ref == \"$branch_name\" and .state == \"open\") | .html_url")
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to open a PR and update it so we can use the PR link for the changelog.yml entry and the PR body table links

bump_integration_version "$changelog_path" "$manifest_path" "$pr_url" "Add CIS rule templates"
git add "$changelog_path" "$manifest_path"
git commit -m "Bump integration version"
git push origin "$branch_name"

# create PR body
rows="$(git diff --name-only origin/main -- "$templates_path" | while read -r file; do jq --arg a "$pr_url/files#diff-$(echo -n "$file" | openssl dgst -sha256 | awk '{print $2}')" -r '.attributes.metadata.benchmark | "\(.id): \(.rule_number): \($a)"' "$file"; done | awk '{split($0, a, ": "); b[a[1]] = (b[a[1]] == "" ? "" : b[a[1]] ", ") "["a[2]"]""("a[3]")"} END {for (i in b) printf("| %s | %s |\n", i, b[i])}')"
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this abomination creates rows with benchmark.id in the 1st column and all of its rules' benchmark.rule_number in the 2nd column. table does look cool though..

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😨

body=$(
cat <<EOF
Added rule templates for CIS rules:
| benchmark.id | benchmark.rule_number |
|--------------|-----------------------|
$rows
EOF
)

# update PR body
gh api \
--method PATCH \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"/repos/elastic/integrations/pulls/$pr_number" \
-f body="$body"
Loading