fix: harden bitbucket-pipelines.yml with security and correctness improvements
This commit is contained in:
+46
-15
@@ -18,6 +18,12 @@
|
|||||||
# Only feature/*, bugfix/*, hotfix/*, dev, stg have PR pipelines defined.
|
# Only feature/*, bugfix/*, hotfix/*, dev, stg have PR pipelines defined.
|
||||||
# Any other branch name simply gets NO PR pipeline → checks won't pass
|
# Any other branch name simply gets NO PR pipeline → checks won't pass
|
||||||
# → PR cannot be merged. No catch-all '**' pattern (that caused double runs).
|
# → PR cannot be merged. No catch-all '**' pattern (that caused double runs).
|
||||||
|
#
|
||||||
|
# SETUP REQUIRED: enforce these gates by enabling "Require a successful
|
||||||
|
# build to merge" for dev, stg, and main in:
|
||||||
|
# Bitbucket → Repository settings → Branch permissions
|
||||||
|
# Without this, the gates are advisory only — PRs can be merged regardless
|
||||||
|
# of whether the gate, lint, tests, or AI review pass or fail.
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
|
|
||||||
@@ -43,7 +49,7 @@ definitions:
|
|||||||
# then syncs both dev and test dependency groups from pyproject.toml
|
# then syncs both dev and test dependency groups from pyproject.toml
|
||||||
- script: &install
|
- script: &install
|
||||||
pip install uv==0.11.5;
|
pip install uv==0.11.5;
|
||||||
uv sync --group dev --group test;
|
uv sync --frozen --group dev --group test;
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Reusable step definitions
|
# Reusable step definitions
|
||||||
@@ -89,12 +95,17 @@ definitions:
|
|||||||
# and posts review comments directly on the pull request.
|
# and posts review comments directly on the pull request.
|
||||||
- step: &ai-code-review
|
- step: &ai-code-review
|
||||||
name: AI Code Review
|
name: AI Code Review
|
||||||
image: python:3.12-slim
|
image: python:3.12.7-slim # slim is fine here — no uv sync
|
||||||
|
# SETUP REQUIRED: configure OIDC provider in
|
||||||
|
# Bitbucket → Workspace settings → Security → OpenID Connect
|
||||||
|
# and add the matching trust policy to the IAM role below.
|
||||||
|
# Without it, the step fails at startup before any script runs.
|
||||||
oidc: true # enables OIDC token injection
|
oidc: true # enables OIDC token injection
|
||||||
script:
|
script:
|
||||||
# Slim image doesn't include git — install it + AWS/HTTP libs
|
# Slim image doesn't include git — install it + AWS/HTTP libs
|
||||||
- apt-get update && apt-get install -y git
|
# --no-install-recommends skips git-man/less/openssh-client etc.
|
||||||
- pip install boto3 requests
|
- apt-get update && apt-get install -y --no-install-recommends git
|
||||||
|
- pip install "boto3==1.35.*" "requests==2.32.*"
|
||||||
|
|
||||||
# --- AWS OIDC authentication setup ---
|
# --- AWS OIDC authentication setup ---
|
||||||
# How it works:
|
# How it works:
|
||||||
@@ -105,16 +116,30 @@ definitions:
|
|||||||
- export AWS_REGION=us-east-1
|
- export AWS_REGION=us-east-1
|
||||||
- export AWS_ROLE_ARN="arn:aws:iam::975049960860:role/DoczyAI-Bitbucket-OIDC"
|
- export AWS_ROLE_ARN="arn:aws:iam::975049960860:role/DoczyAI-Bitbucket-OIDC"
|
||||||
- export AWS_WEB_IDENTITY_TOKEN_FILE="$(pwd)/web-identity-token"
|
- export AWS_WEB_IDENTITY_TOKEN_FILE="$(pwd)/web-identity-token"
|
||||||
- echo "$BITBUCKET_STEP_OIDC_TOKEN" > "$(pwd)/web-identity-token"
|
- printf '%s' "$BITBUCKET_STEP_OIDC_TOKEN" > "$(pwd)/web-identity-token"
|
||||||
# ↑ Quoted to prevent word-splitting if token has special chars
|
# printf (not echo) avoids appending a trailing newline to the JWT
|
||||||
|
|
||||||
# Clone the review agent repo (private — needs BITBUCKET_CLONE_TOKEN)
|
# Clone the review agent repo (private — needs BITBUCKET_CLONE_TOKEN)
|
||||||
|
# SETUP REQUIRED: set BITBUCKET_CLONE_TOKEN (marked "Secured") in
|
||||||
|
# Bitbucket → Repository settings → Repository variables
|
||||||
|
# Guard against missing token so failure is self-diagnosing
|
||||||
|
- |
|
||||||
|
if [ -z "${BITBUCKET_CLONE_TOKEN}" ]; then
|
||||||
|
echo "BLOCKED: BITBUCKET_CLONE_TOKEN not set."
|
||||||
|
echo "Set it in Repository settings → Repository variables (Secured)."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
# -q flag suppresses URL output to avoid leaking the token in logs
|
# -q flag suppresses URL output to avoid leaking the token in logs
|
||||||
- git clone -q "https://x-token-auth:${BITBUCKET_CLONE_TOKEN}@bitbucket.org/${BITBUCKET_WORKSPACE}/code-review-agent.git"
|
- git clone -q "https://x-token-auth:${BITBUCKET_CLONE_TOKEN}@bitbucket.org/${BITBUCKET_WORKSPACE}/code-review-agent.git"
|
||||||
|
|
||||||
# Build the PR URL and run the agent
|
# Build the PR URL and run the agent.
|
||||||
|
# The || echo below only covers the python main.py call:
|
||||||
|
# agent crashes, AWS STS errors, and runtime exceptions become
|
||||||
|
# warnings instead of hard failures. A failure in git clone or
|
||||||
|
# apt-get above is still a hard failure (rare in practice).
|
||||||
|
# AI review is advisory, not a gate.
|
||||||
- export PR_URL="https://bitbucket.org/${BITBUCKET_WORKSPACE}/${BITBUCKET_REPO_SLUG}/pull-requests/${BITBUCKET_PR_ID}"
|
- export PR_URL="https://bitbucket.org/${BITBUCKET_WORKSPACE}/${BITBUCKET_REPO_SLUG}/pull-requests/${BITBUCKET_PR_ID}"
|
||||||
- cd code-review-agent && python main.py "$PR_URL"
|
- cd code-review-agent && python main.py "$PR_URL" || echo "WARNING: AI review step failed (non-blocking)"
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -139,7 +164,7 @@ pipelines:
|
|||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
# Each entry matches a SOURCE branch pattern (the branch the PR comes
|
# Each entry matches a SOURCE branch pattern (the branch the PR comes
|
||||||
# FROM, not the branch it targets). The gate step inside validates
|
# FROM, not the branch it targets). The gate step inside validates
|
||||||
# the DESTINATION branch using ALLOWED_TARGETS.
|
# the DESTINATION branch.
|
||||||
#
|
#
|
||||||
# Flow for every PR:
|
# Flow for every PR:
|
||||||
# Step 1: Gate → is this PR targeting the correct branch?
|
# Step 1: Gate → is this PR targeting the correct branch?
|
||||||
@@ -356,7 +381,10 @@ pipelines:
|
|||||||
patch=$((patch + 1))
|
patch=$((patch + 1))
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Create the annotated tag and push it to the remote
|
# Create the annotated tag and push it to the remote.
|
||||||
|
# SETUP REQUIRED: enable "Repository write access" in
|
||||||
|
# Bitbucket → Repository settings → Pipelines
|
||||||
|
# Without it, git push fails with 403 and releases don't work.
|
||||||
- |
|
- |
|
||||||
new_tag="v${major}.${minor}.${patch}"
|
new_tag="v${major}.${minor}.${patch}"
|
||||||
echo "Creating tag: ${new_tag}"
|
echo "Creating tag: ${new_tag}"
|
||||||
@@ -375,7 +403,7 @@ pipelines:
|
|||||||
image: atlassian/default-image:4
|
image: atlassian/default-image:4
|
||||||
script:
|
script:
|
||||||
# Read the tag that was created in step 1
|
# Read the tag that was created in step 1
|
||||||
- export RELEASE_TAG=$(cat release_tag.txt)
|
- export RELEASE_TAG="$(cat release_tag.txt)"
|
||||||
- echo "Deploying ${RELEASE_TAG}"
|
- echo "Deploying ${RELEASE_TAG}"
|
||||||
|
|
||||||
# DEPLOY_COMMAND is a repository variable configured in
|
# DEPLOY_COMMAND is a repository variable configured in
|
||||||
@@ -400,7 +428,7 @@ pipelines:
|
|||||||
rollback-prod:
|
rollback-prod:
|
||||||
- variables:
|
- variables:
|
||||||
- name: ROLLBACK_TAG
|
- name: ROLLBACK_TAG
|
||||||
default: v0.0.0 # user must change this to a real tag
|
default: "" # user MUST provide a real tag (e.g. v1.2.0)
|
||||||
|
|
||||||
# Step 1: Validate the tag exists
|
# Step 1: Validate the tag exists
|
||||||
- step:
|
- step:
|
||||||
@@ -422,11 +450,14 @@ pipelines:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Verify the tag exists in the remote
|
# Verify the ref exists AND is specifically a tag (not a branch,
|
||||||
|
# HEAD, or commit hash). git rev-parse would accept any ref;
|
||||||
|
# `git tag -l` + exact-match grep ensures we only accept tags.
|
||||||
- git fetch --tags --force
|
- git fetch --tags --force
|
||||||
- |
|
- |
|
||||||
if ! git rev-parse "${ROLLBACK_TAG}" >/dev/null 2>&1; then
|
if ! git tag -l "${ROLLBACK_TAG}" | grep -qxF "${ROLLBACK_TAG}"; then
|
||||||
echo "BLOCKED: tag ${ROLLBACK_TAG} does not exist."
|
echo "BLOCKED: ${ROLLBACK_TAG} is not a tag."
|
||||||
|
echo "Rollback requires a release tag (e.g. v1.2.0), not a branch/commit."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
- echo "Tag ${ROLLBACK_TAG} found — proceeding with rollback."
|
- echo "Tag ${ROLLBACK_TAG} found — proceeding with rollback."
|
||||||
|
|||||||
Reference in New Issue
Block a user