Skip to content

fix: resolved cleanup noetype issue #177

fix: resolved cleanup noetype issue

fix: resolved cleanup noetype issue #177

Workflow file for this run

name: Ansible Deployment
# This workflow automates the deployment of NetBird using Ansible.
# It supports two deployment targets: Remote SSH and AWS SSM.
#
# REQUIRED GITHUB SECRETS:
# (See documentation in the repository for a full list of NetBird specific secrets)
#
# FOR 'aws_ssm': AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, AWS_INSTANCE_ID
# FOR 'ssh_remote': ANSIBLE_SSH_KEY, ANSIBLE_HOST, ANSIBLE_USER
#
# KEYCLOAK AUTOMATION:
# KEYCLOAK_URL: URL of existing Keycloak instance (e.g., https://keycloak.example.com)
# KEYCLOAK_ADMIN_USER: Keycloak admin username (default: admin)
# KEYCLOAK_ADMIN_PASSWORD: Keycloak admin password
on:
push:
branches:
- main
- feature/ansible-netbird-deployment-pipeline
paths:
- 'infrastructure/ansible/**'
- '.github/workflows/ansible-deploy.yml'
pull_request:
branches:
- main
paths:
- 'infrastructure/ansible/**'
- '.github/workflows/ansible-deploy.yml'
workflow_dispatch:
inputs:
action:
description: 'Action to perform'
required: true
default: 'deploy'
type: choice
options:
- deploy
- cleanup
deployment_target:
description: 'Where to deploy/cleanup NetBird'
required: true
default: 'aws_ssm'
type: choice
options:
- ssh_remote
- aws_ssm
target_host:
description: 'Target Host (SSH IP or AWS Instance ID)'
required: false
target_user:
description: 'Target User (e.g., ubuntu, ec2-user)'
required: false
default: 'ubuntu'
netbird_domain:
description: 'NetBird Domain (e.g., netbird.example.com)'
required: false
netbird_default_password:
description: 'Initial Admin Password (optional)'
required: false
keycloak_url:
description: 'Keycloak URL (if using external Keycloak)'
required: false
jobs:
deploy:
name: Deploy NetBird
runs-on: ubuntu-latest
env:
ACTION: ${{ github.event.inputs.action || vars.DEPLOY_ACTION || 'deploy' }}
DEPLOY_TARGET: ${{ github.event.inputs.deployment_target || vars.DEPLOY_TARGET || 'aws_ssm' }}
KEYCLOAK_URL_SECRET: ${{ github.event.inputs.keycloak_url || secrets.KEYCLOAK_URL }}
KEYCLOAK_ADMIN_USER_SECRET: ${{ secrets.KEYCLOAK_ADMIN_USER || 'admin' }}
KEYCLOAK_ADMIN_PASSWORD_SECRET: ${{ secrets.KEYCLOAK_ADMIN_PASSWORD }}
AWS_SESSION_MANAGER_PLUGIN: /usr/local/bin/session-manager-plugin
PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install Ansible and Dependencies
run: |
python -m pip install --upgrade pip
pip install ansible botocore boto3
- name: Install Ansible Collections
run: |
ansible-galaxy collection install community.docker --upgrade
ansible-galaxy collection install community.aws --upgrade
ansible-galaxy collection install amazon.aws --upgrade
- name: Verify AWS CLI and Plugin
run: |
which aws
aws --version
which session-manager-plugin
session-manager-plugin --version
- name: Diagnostic - Verify Required Secrets
run: |
MISSING=()
[ -z "${{ secrets.NETBIRD_DOMAIN }}" ] && MISSING+=("NETBIRD_DOMAIN")
[ -z "${{ secrets.KEYCLOAK_URL }}" ] && MISSING+=("KEYCLOAK_URL")
[ -z "${{ secrets.KEYCLOAK_ADMIN_PASSWORD }}" ] && MISSING+=("KEYCLOAK_ADMIN_PASSWORD")
# Auto-extract KEYCLOAK_DOMAIN if missing
KC_URL="${{ github.event.inputs.keycloak_url || secrets.KEYCLOAK_URL }}"
KC_DOMAIN="${{ secrets.KEYCLOAK_DOMAIN }}"
if [ -z "$KC_DOMAIN" ] && [ -n "$KC_URL" ]; then
KC_DOMAIN=$(echo "$KC_URL" | awk -F[/:] '{print $4}')
echo "KC_DOMAIN_EXTRACTED=$KC_DOMAIN" >> $GITHUB_ENV
echo "Extracted KEYCLOAK_DOMAIN: $KC_DOMAIN"
else
echo "KC_DOMAIN_EXTRACTED=$KC_DOMAIN" >> $GITHUB_ENV
fi
if [ ${#MISSING[@]} -ne 0 ]; then
for var in "${MISSING[@]}"; do
echo "::error::Missing required secret: $var"
done
exit 1
fi
if [ -z "$KC_DOMAIN" ] && [ -z "$KC_URL" ]; then
echo "::error::Missing required secret: KEYCLOAK_DOMAIN (or KEYCLOAK_URL for extraction)"
exit 1
fi
echo "All core deployment secrets are present."
- name: Diagnostic - Verify AWS Secrets Presence
if: env.DEPLOY_TARGET == 'aws_ssm'
run: |
if [ -z "$AWS_ID" ]; then echo "::error::AWS_ACCESS_KEY_ID is empty"; else echo "AWS_ACCESS_KEY_ID is set (length: ${#AWS_ID})"; fi
if [ -z "$AWS_SECRET" ]; then echo "::error::AWS_SECRET_ACCESS_KEY is empty"; else echo "AWS_SECRET_ACCESS_KEY is set (length: ${#AWS_SECRET})"; fi
if [ -z "$AWS_REGION" ]; then echo "::warning::AWS_REGION is empty, defaulting to us-east-1"; else echo "AWS_REGION is set to $AWS_REGION"; fi
env:
AWS_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION: ${{ secrets.AWS_REGION }}
- name: Configure AWS Credentials
if: env.DEPLOY_TARGET == 'aws_ssm'
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION || 'us-east-1' }}
mask-aws-account-id: 'true'
- name: Install Session Manager Plugin
if: env.DEPLOY_TARGET == 'aws_ssm'
run: |
curl "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/ubuntu_64bit/session-manager-plugin.deb" -o "session-manager-plugin.deb"
sudo dpkg -i session-manager-plugin.deb
which session-manager-plugin
session-manager-plugin --version
- name: Setup SSH
if: env.DEPLOY_TARGET == 'ssh_remote'
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.ANSIBLE_SSH_KEY }}
- name: Configure Keycloak for NetBird
if: env.ACTION == 'deploy' && env.KEYCLOAK_URL_SECRET != '' && env.KEYCLOAK_ADMIN_PASSWORD_SECRET != ''
env:
KEYCLOAK_URL: ${{ github.event.inputs.keycloak_url || secrets.KEYCLOAK_URL }}
KEYCLOAK_ADMIN_USER: ${{ secrets.KEYCLOAK_ADMIN_USER || 'admin' }}
KEYCLOAK_ADMIN_PASSWORD: ${{ secrets.KEYCLOAK_ADMIN_PASSWORD }}
NETBIRD_DOMAIN: ${{ github.event.inputs.netbird_domain || secrets.NETBIRD_DOMAIN }}
NETBIRD_REALM: ${{ secrets.NETBIRD_REALM || 'netbird' }}
NETBIRD_DEFAULT_USER: ${{ secrets.NETBIRD_DEFAULT_USER || 'admin' }}
NETBIRD_DEFAULT_PASSWORD: ${{ github.event.inputs.netbird_default_password || secrets.NETBIRD_DEFAULT_PASSWORD }}
NETBIRD_MANAGEMENT_SECRET: ${{ secrets.NETBIRD_MANAGEMENT_SECRET }}
NETBIRD_RELAY_SECRET: ${{ secrets.NETBIRD_RELAY_SECRET }}
NETBIRD_TURN_SECRET: ${{ secrets.NETBIRD_TURN_SECRET }}
NETBIRD_TURN_PASSWORD: ${{ secrets.NETBIRD_TURN_PASSWORD }}
NETBIRD_DATASTORE_ENCRYPTION_KEY: ${{ secrets.NETBIRD_DATASTORE_ENCRYPTION_KEY }}
OUTPUT_FORMAT: json
run: |
chmod +x infrastructure/scripts/keycloak-setup.sh
infrastructure/scripts/keycloak-setup.sh > keycloak-output.json
echo "Keycloak configuration completed"
cat keycloak-output.json
- name: Load Keycloak Configuration
if: env.ACTION == 'deploy'
id: keycloak_config
run: |
if [ -s keycloak-output.json ] && jq '.' keycloak-output.json > /dev/null 2>&1; then
echo "Using Keycloak automation output"
echo "KEYCLOAK_AUTOMATED=true" >> $GITHUB_ENV
# Extract values from JSON and ensure they are not empty
{
echo "KC_CLIENT_ID=$(jq -r '.netbird_client_id // empty' keycloak-output.json)"
echo "KC_MGMT_CLIENT_ID=$(jq -r '.netbird_idp_mgmt_client_id // empty' keycloak-output.json)"
echo "KC_MGMT_CLIENT_SECRET=$(jq -r '.netbird_idp_mgmt_client_secret // empty' keycloak-output.json)"
echo "KC_MANAGEMENT_SECRET=$(jq -r '.netbird_management_secret // empty' keycloak-output.json)"
echo "KC_RELAY_SECRET=$(jq -r '.netbird_relay_secret // empty' keycloak-output.json)"
echo "KC_TURN_SECRET=$(jq -r '.netbird_turn_secret // empty' keycloak-output.json)"
echo "KC_TURN_PASSWORD=$(jq -r '.netbird_turn_password // empty' keycloak-output.json)"
echo "KC_DATASTORE_KEY=$(jq -r '.netbird_datastore_encryption_key // empty' keycloak-output.json)"
echo "KC_REALM=$(jq -r '.netbird_realm // empty' keycloak-output.json)"
echo "KC_DOMAIN=$(jq -r '.keycloak_domain // empty' keycloak-output.json)"
echo "KC_AUTHORITY=$(jq -r '.netbird_auth_authority // empty' keycloak-output.json)"
} >> $GITHUB_ENV
else
echo "Keycloak automation output missing or invalid. Falling back to GitHub Secrets."
echo "KEYCLOAK_AUTOMATED=false" >> $GITHUB_ENV
fi
- name: Run Ansible Playbook
if: env.ACTION == 'deploy'
env:
TARGET_HOST: ${{ github.event.inputs.target_host || secrets.ANSIBLE_HOST }}
TARGET_USER: ${{ github.event.inputs.target_user || secrets.ANSIBLE_USER || 'ubuntu' }}
NETBIRD_DOMAIN: ${{ github.event.inputs.netbird_domain || secrets.NETBIRD_DOMAIN }}
run: |
# Create a secure variables file using jq to prevent shell injection
jq -n \
--arg domain "$NETBIRD_DOMAIN" \
--arg email "${{ secrets.NETBIRD_CADDY_EMAIL }}" \
--arg ip "${{ secrets.NETBIRD_EXTERNAL_IP }}" \
--arg idp "${{ secrets.IDP_SERVICE_NAME || 'keycloak' }}" \
--arg port "${{ secrets.KEYCLOAK_PORT || '8080' }}" \
--arg auth_url "${{ github.event.inputs.keycloak_url || secrets.KEYCLOAK_URL }}" \
--arg admin_user "${{ secrets.KEYCLOAK_ADMIN_USER || 'admin' }}" \
--arg admin_pass "${{ secrets.KEYCLOAK_ADMIN_PASSWORD }}" \
--arg kc_domain "${{ env.KC_DOMAIN_EXTRACTED }}" \
--arg realm "${{ secrets.NETBIRD_REALM || 'netbird' }}" \
--arg def_pass "${{ github.event.inputs.netbird_default_password || secrets.NETBIRD_DEFAULT_PASSWORD }}" \
--arg s3_bucket "${{ secrets.AWS_S3_BUCKET }}" \
--arg aws_region "${{ secrets.AWS_REGION || 'us-east-1' }}" \
'{
netbird_domain: $domain,
netbird_caddy_email: $email,
netbird_external_ip: $ip,
idp_service_name: $idp,
keycloak_port: $port,
keycloak_auth_url: $auth_url,
keycloak_admin_user: $admin_user,
keycloak_admin_password: $admin_pass,
keycloak_domain: $kc_domain,
netbird_realm: $realm,
netbird_default_password: $def_pass,
ansible_aws_ssm_s3_bucket: $s3_bucket,
ansible_aws_ssm_bucket_name: $s3_bucket,
ansible_aws_ssm_region: $aws_region,
ansible_aws_ssm_s3_region: $aws_region,
ansible_shell_type: "sh",
ansible_remote_tmp: "/tmp"
}' > extra_vars.json
# Remove S3 bucket from vars if empty to prevent SSM plugin errors
if [ -z "${{ secrets.AWS_S3_BUCKET }}" ]; then
jq 'del(.ansible_aws_ssm_s3_bucket) | del(.ansible_aws_ssm_bucket_name) | del(.ansible_aws_ssm_s3_region) | del(.ansible_aws_ssm_region)' extra_vars.json > extra_vars.json.tmp && mv extra_vars.json.tmp extra_vars.json
fi
# Function to add secret only if not empty
add_secret() {
local name=$1
local value=$2
if [ -n "$value" ]; then
jq --arg name "$name" --arg value "$value" '. + {($name): $value}' extra_vars.json > extra_vars.json.tmp && mv extra_vars.json.tmp extra_vars.json
fi
}
add_secret "netbird_auth_audience" "${{ secrets.NETBIRD_AUTH_AUDIENCE }}"
add_secret "netbird_auth_client_id" "${{ secrets.NETBIRD_AUTH_CLIENT_ID }}"
add_secret "netbird_auth_authority" "${{ secrets.NETBIRD_AUTH_AUTHORITY }}"
add_secret "netbird_management_secret" "${{ secrets.NETBIRD_MANAGEMENT_SECRET }}"
add_secret "netbird_relay_secret" "${{ secrets.NETBIRD_RELAY_SECRET }}"
add_secret "netbird_turn_secret" "${{ secrets.NETBIRD_TURN_SECRET }}"
add_secret "netbird_turn_password" "${{ secrets.NETBIRD_TURN_PASSWORD }}"
add_secret "netbird_datastore_encryption_key" "${{ secrets.NETBIRD_DATASTORE_ENCRYPTION_KEY }}"
add_secret "netbird_idp_mgmt_client_id" "${{ secrets.NETBIRD_IDP_MGMT_CLIENT_ID }}"
add_secret "netbird_idp_mgmt_client_secret" "${{ secrets.NETBIRD_IDP_MGMT_CLIENT_SECRET }}"
add_secret "netbird_default_user" "${{ secrets.NETBIRD_DEFAULT_USER || 'admin' }}"
add_secret "netbird_default_password" "${{ secrets.NETBIRD_DEFAULT_PASSWORD }}"
echo "[netbird_servers]" > inventory_gh.ini
if [ "$DEPLOY_TARGET" == "aws_ssm" ]; then
INSTANCE_ID="${{ github.event.inputs.target_host || secrets.AWS_INSTANCE_ID }}"
REGION="${{ secrets.AWS_REGION || 'us-east-1' }}"
USER_TO_USE="${{ github.event.inputs.target_user || secrets.ANSIBLE_USER || 'ubuntu' }}"
echo "Debugging AWS SSM Connection:"
echo "Target Instance ID: $INSTANCE_ID"
echo "Target Region: $REGION"
echo "Target User: $USER_TO_USE"
if [ -z "$INSTANCE_ID" ]; then
echo "::error::AWS Instance ID is missing. Provide it via 'target_host' input or AWS_INSTANCE_ID secret."
exit 1
fi
if [ -z "$REGION" ]; then
echo "::error::AWS Region is missing. Ensure AWS_REGION secret is set."
exit 1
fi
echo "netbird-primary ansible_host=$INSTANCE_ID ansible_connection=aws_ssm ansible_aws_ssm_region=$REGION ansible_user=$USER_TO_USE ansible_aws_ssm_timeout=120" >> inventory_gh.ini
else
echo "netbird-primary ansible_host=$TARGET_HOST ansible_user=$TARGET_USER ansible_ssh_common_args='-o StrictHostKeyChecking=no'" >> inventory_gh.ini
fi
# Build ansible-playbook command using the JSON vars file
PLAYBOOK_CMD="ansible-playbook -vvvv -i inventory_gh.ini infrastructure/ansible/playbook.yaml -e @extra_vars.json"
if [ "$KEYCLOAK_AUTOMATED" = "true" ]; then
echo "Merging Keycloak automation values"
# Build an object with only non-empty KC values
KC_VARS=$(jq -n \
--arg client_id "$KC_CLIENT_ID" \
--arg mgmt_client_id "$KC_MGMT_CLIENT_ID" \
--arg mgmt_client_secret "$KC_MGMT_CLIENT_SECRET" \
--arg mgmt_secret "$KC_MANAGEMENT_SECRET" \
--arg relay_secret "$KC_RELAY_SECRET" \
--arg turn_secret "$KC_TURN_SECRET" \
--arg turn_password "$KC_TURN_PASSWORD" \
--arg datastore_key "$KC_DATASTORE_KEY" \
--arg realm "$KC_REALM" \
--arg domain "$KC_DOMAIN" \
--arg authority "$KC_AUTHORITY" \
'{}
| if $client_id != "" then . + {netbird_auth_client_id: $client_id, netbird_auth_audience: $client_id} else . end
| if $mgmt_client_id != "" then . + {netbird_idp_mgmt_client_id: $mgmt_client_id} else . end
| if $mgmt_client_secret != "" then . + {netbird_idp_mgmt_client_secret: $mgmt_client_secret} else . end
| if $mgmt_secret != "" then . + {netbird_management_secret: $mgmt_secret} else . end
| if $relay_secret != "" then . + {netbird_relay_secret: $relay_secret} else . end
| if $turn_secret != "" then . + {netbird_turn_secret: $turn_secret} else . end
| if $turn_password != "" then . + {netbird_turn_password: $turn_password} else . end
| if $datastore_key != "" then . + {netbird_datastore_encryption_key: $datastore_key} else . end
| if $realm != "" then . + {netbird_realm: $realm} else . end
| if $domain != "" then . + {keycloak_domain: $domain} else . end
| if $authority != "" then . + {netbird_auth_authority: $authority} else . end')
# Save KC vars to another file and use it
echo "$KC_VARS" > kc_vars.json
PLAYBOOK_CMD="$PLAYBOOK_CMD -e @kc_vars.json"
fi
# Execute the command
eval $PLAYBOOK_CMD
- name: Run Cleanup Playbook
if: env.ACTION == 'cleanup'
env:
TARGET_HOST: ${{ github.event.inputs.target_host || secrets.ANSIBLE_HOST }}
TARGET_USER: ${{ github.event.inputs.target_user || secrets.ANSIBLE_USER || 'ubuntu' }}
run: |
# Create a secure variables file for cleanup
jq -n \
--arg auth_url "${{ github.event.inputs.keycloak_url || secrets.KEYCLOAK_URL }}" \
--arg admin_user "${{ secrets.KEYCLOAK_ADMIN_USER || 'admin' }}" \
--arg admin_pass "${{ secrets.KEYCLOAK_ADMIN_PASSWORD }}" \
--arg realm "${{ secrets.NETBIRD_REALM || 'netbird' }}" \
--arg validate_certs "${{ secrets.KEYCLOAK_VALIDATE_CERTS || 'true' }}" \
--arg s3_bucket "${{ secrets.AWS_S3_BUCKET }}" \
--arg aws_region "${{ secrets.AWS_REGION || 'us-east-1' }}" \
'{
keycloak_auth_url: $auth_url,
keycloak_admin_user: $admin_user,
keycloak_admin_password: $admin_pass,
netbird_realm: $realm,
keycloak_validate_certs: ($validate_certs == "true"),
ansible_aws_ssm_s3_bucket: $s3_bucket,
ansible_aws_ssm_bucket_name: $s3_bucket,
ansible_aws_ssm_region: $aws_region,
ansible_aws_ssm_s3_region: $aws_region,
ansible_remote_tmp: "/tmp",
ansible_shell_type: "sh"
}' > cleanup_vars.json
# Remove S3 bucket from vars if empty to prevent SSM plugin errors
if [ -z "${{ secrets.AWS_S3_BUCKET }}" ]; then
jq 'del(.ansible_aws_ssm_s3_bucket) | del(.ansible_aws_ssm_bucket_name) | del(.ansible_aws_ssm_s3_region) | del(.ansible_aws_ssm_region)' cleanup_vars.json > cleanup_vars.json.tmp && mv cleanup_vars.json.tmp cleanup_vars.json
fi
echo "[netbird_servers]" > inventory_gh.ini
if [ "$DEPLOY_TARGET" == "aws_ssm" ]; then
INSTANCE_ID="${{ github.event.inputs.target_host || secrets.AWS_INSTANCE_ID }}"
REGION="${{ secrets.AWS_REGION || 'us-east-1' }}"
USER_TO_USE="${{ github.event.inputs.target_user || secrets.ANSIBLE_USER || 'ubuntu' }}"
echo "netbird-primary ansible_host=$INSTANCE_ID ansible_connection=aws_ssm ansible_aws_ssm_region=$REGION ansible_user=$USER_TO_USE ansible_aws_ssm_timeout=120" >> inventory_gh.ini
else
echo "netbird-primary ansible_host=$TARGET_HOST ansible_user=$TARGET_USER ansible_ssh_common_args='-o StrictHostKeyChecking=no'" >> inventory_gh.ini
fi
ansible-playbook -vvvv -i inventory_gh.ini infrastructure/ansible/playbook.yaml --tags cleanup -e @cleanup_vars.json