Runners

Security Hardening for Self-Hosted Runners

Comprehensive security best practices for enterprise runner deployments


This guide provides enterprise-grade security hardening practices for self-hosted runners across all major CI/CD platforms. Whether you're deploying GitHub Actions runners, GitLab Runners, or Jenkins agents, these security measures will help you build a robust and compliant infrastructure.

Security Overview#

Threat Model#

Self-hosted runners face several attack vectors that require comprehensive security measures:

External threats:

  • Remote code execution through compromised workflows
  • Credential theft from insecure storage
  • Network-based attacks on runner infrastructure
  • Supply chain attacks through dependencies

Internal threats:

  • Privilege escalation by malicious workflows
  • Data exfiltration through runner access
  • Cross-tenant contamination in shared environments
  • Insider threats with excessive permissions

Infrastructure risks:

  • Unpatched operating systems and dependencies
  • Misconfigured network access controls
  • Insecure container configurations
  • Weak authentication mechanisms

Attack Vectors#

Understanding common attack patterns helps prioritize security controls:

  1. Workflow injection attacks - Malicious code execution through PR workflows
  2. Token theft - Stealing runner registration or API tokens
  3. Privilege escalation - Exploiting runner permissions to access broader infrastructure
  4. Data persistence attacks - Leaving malicious code or data between job runs
  5. Network pivoting - Using compromised runners to attack internal systems

Network Security#

Network Isolation#

Implement defense-in-depth network security:

1
# Create isolated network for runners
2
sudo ip netns add runner-isolation
3
sudo ip link add veth0 type veth peer name veth1
4
sudo ip link set veth1 netns runner-isolation
5
6
# Configure firewall rules for runner traffic
7
sudo iptables -N RUNNER_CHAIN
8
sudo iptables -A INPUT -j RUNNER_CHAIN
9
sudo iptables -A RUNNER_CHAIN -p tcp --dport 443 -j ACCEPT # HTTPS only
10
sudo iptables -A RUNNER_CHAIN -p tcp --dport 22 -s 10.0.0.0/8 -j ACCEPT # SSH from internal
11
sudo iptables -A RUNNER_CHAIN -j DROP # Drop everything else

VPN Configuration#

Use WireGuard for secure runner-to-cloud connectivity:

1
# Generate WireGuard configuration
2
wg genkey | tee /etc/wireguard/runner-private.key | wg pubkey > /etc/wireguard/runner-public.key
3
4
# Create WireGuard configuration
5
cat > /etc/wireguard/wg0.conf << EOF
6
[Interface]
7
PrivateKey = $(cat /etc/wireguard/runner-private.key)
8
Address = 10.200.200.2/24
9
DNS = 1.1.1.1
10
11
[Peer]
12
PublicKey = YOUR_SERVER_PUBLIC_KEY
13
Endpoint = vpn.assistance.bg:51820
14
AllowedIPs = 10.200.200.0/24, 192.168.1.0/24
15
PersistentKeepalive = 25
16
EOF
17
18
# Start WireGuard
19
sudo systemctl enable wg-quick@wg0
20
sudo systemctl start wg-quick@wg0

Firewall Configuration#

Platform-specific firewall rules:

Linux (iptables):

1
#!/bin/bash
2
# Runner firewall configuration
3
4
# Flush existing rules
5
iptables -F
6
iptables -X
7
iptables -t nat -F
8
iptables -t nat -X
9
10
# Default policies
11
iptables -P INPUT DROP
12
iptables -P FORWARD DROP
13
iptables -P OUTPUT ACCEPT
14
15
# Allow loopback
16
iptables -A INPUT -i lo -j ACCEPT
17
18
# Allow established connections
19
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
20
21
# SSH access (restricted)
22
iptables -A INPUT -p tcp --dport 22 -s 192.168.1.0/24 -j ACCEPT
23
24
# HTTPS for API calls
25
iptables -A OUTPUT -p tcp --dport 443 -j ACCEPT
26
27
# DNS
28
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
29
30
# Save rules
31
iptables-save > /etc/iptables/rules.v4

macOS (pfctl):

1
# Create pf.conf for macOS runners
2
cat > /etc/pf.conf << EOF
3
# Default deny
4
block all
5
6
# Allow loopback
7
pass on lo0
8
9
# Allow established connections
10
pass in proto tcp from any to any port ssh flags S/SA keep state
11
pass out proto tcp from any to any port https keep state
12
pass out proto udp from any to any port domain keep state
13
14
# Block direct internet access except HTTPS
15
block out inet proto tcp from any to any port != https
16
EOF
17
18
# Load configuration
19
sudo pfctl -f /etc/pf.conf
20
sudo pfctl -e

Zero-Trust Architecture#

Implement zero-trust networking for distributed runners:

1
# Istio service mesh configuration for Kubernetes runners
2
apiVersion: security.istio.io/v1beta1
3
kind: AuthorizationPolicy
4
metadata:
5
name: runner-access-control
6
spec:
7
selector:
8
matchLabels:
9
app: self-hosted-runner
10
rules:
11
- from:
12
- source:
13
principals: ["cluster.local/ns/runners/sa/runner-service-account"]
14
- to:
15
- operation:
16
methods: ["GET", "POST"]
17
paths: ["/api/v2/*"]
18
- when:
19
- key: source.ip
20
values: ["10.200.200.0/24"]

Platform-Specific Hardening#

GitHub Actions Security#

Runner Group Isolation:

1
# Configure runner with restricted group access
2
./config.sh --url https://github.com/your-org --token $RUNNER_TOKEN \
3
--runnergroup "secure-runners" --labels "hardened,production" \
4
--replace --disableupdate

Token Management:

1
#!/bin/bash
2
# GitHub runner token rotation script
3
4
GITHUB_ORG="your-organization"
5
GITHUB_TOKEN="$GITHUB_ADMIN_TOKEN"
6
7
# Generate new runner token
8
NEW_TOKEN=$(curl -X POST \
9
-H "Authorization: token $GITHUB_TOKEN" \
10
-H "Accept: application/vnd.github.v3+json" \
11
https://api.github.com/orgs/$GITHUB_ORG/actions/runners/registration-token | \
12
jq -r .token)
13
14
# Update runner configuration
15
./config.sh remove --token $OLD_TOKEN
16
./config.sh --url https://github.com/$GITHUB_ORG --token $NEW_TOKEN
17
18
echo "Runner token rotated successfully"

Workflow Security:

1
# .github/workflows/security-policy.yml
2
name: Security Policy Enforcement
3
on:
4
workflow_run:
5
workflows: ["*"]
6
types: [requested]
7
8
jobs:
9
security-check:
10
runs-on: [self-hosted, hardened]
11
steps:
12
- name: Validate workflow permissions
13
run: |
14
# Check for dangerous permissions
15
if echo "${{ toJson(github.event.workflow_run.head_commit.message) }}" | grep -E "(sudo|rm -rf|chmod 777)"; then
16
echo "::error::Dangerous commands detected in workflow"
17
exit 1
18
fi

GitLab Runner Security#

Secure Runner Registration:

1
# Register GitLab runner with security constraints
2
gitlab-runner register \
3
--url "https://gitlab.assistance.bg/" \
4
--registration-token "$GITLAB_REGISTRATION_TOKEN" \
5
--executor "docker" \
6
--docker-image "alpine:latest" \
7
--docker-privileged=false \
8
--docker-disable-cache=true \
9
--docker-volumes="/var/run/docker.sock:/var/run/docker.sock:ro" \
10
--docker-security-opt="no-new-privileges:true" \
11
--docker-security-opt="apparmor=docker-default" \
12
--tag-list "secure,hardened" \
13
--run-untagged=false \
14
--locked=true

Executor Security:

1
# /etc/gitlab-runner/config.toml
2
concurrent = 2
3
check_interval = 0
4
5
[session_server]
6
session_timeout = 1800
7
8
[[runners]]
9
name = "secure-docker-runner"
10
url = "https://gitlab.assistance.bg/"
11
token = "SECURE_TOKEN"
12
executor = "docker"
13
[runners.docker]
14
tls_verify = false
15
image = "alpine:latest"
16
privileged = false
17
disable_cache = true
18
volumes = ["/cache", "/var/run/docker.sock:/var/run/docker.sock:ro"]
19
security_opt = ["no-new-privileges:true", "apparmor=docker-default"]
20
cap_drop = ["ALL"]
21
cap_add = ["CHOWN", "SETUID", "SETGID"]
22
devices = []
23
network_mode = "bridge"
24
dns = ["1.1.1.1", "8.8.8.8"]
25
pull_policy = "always"

Jenkins Security#

Agent Configuration:

1
// Jenkins agent security configuration
2
import jenkins.model.*
3
import hudson.model.*
4
import hudson.slaves.*
5
import hudson.plugins.sshslaves.*
6
7
def jenkins = Jenkins.getInstance()
8
9
// Create secure node
10
def launcher = new SSHLauncher(
11
"runner.assistance.bg", // host
12
22, // port
13
"jenkins-ssh-key", // credentials ID
14
"", // JVM options
15
"", // Java path
16
"", // prefix start slave command
17
"", // suffix start slave command
18
60, // launch timeout
19
3, // max retries
20
15 // retry wait time
21
)
22
23
def node = new DumbSlave(
24
"secure-agent", // node name
25
"Hardened Jenkins agent", // description
26
"/home/jenkins", // remote FS root
27
"2", // executors
28
Node.Mode.EXCLUSIVE, // usage mode
29
"hardened production", // labels
30
launcher,
31
new RetentionStrategy.Always(),
32
new LinkedList<NodeProperty<?>>()
33
)
34
35
jenkins.addNode(node)
36
jenkins.save()

Script Security:

1
// Jenkinsfile with security constraints
2
pipeline {
3
agent { label 'hardened' }
4
5
options {
6
skipDefaultCheckout()
7
timeout(time: 30, unit: 'MINUTES')
8
timestamps()
9
}
10
11
environment {
12
// Restrict environment variables
13
PATH = "/usr/local/bin:/usr/bin:/bin"
14
JAVA_OPTS = "-Xmx1g -Djava.awt.headless=true"
15
}
16
17
stages {
18
stage('Security Check') {
19
steps {
20
script {
21
// Validate build parameters
22
if (params.containsKey('DANGEROUS_PARAM')) {
23
error("Dangerous parameter detected")
24
}
25
}
26
}
27
}
28
}
29
}

Bazel Remote Execution#

gRPC Security Configuration:

1
# Start Bazel Remote Execution with TLS
2
bazel run //server:remote_execution_server -- \
3
--port=8980 \
4
--tls_certificate=/etc/ssl/certs/server.crt \
5
--tls_private_key=/etc/ssl/private/server.key \
6
--tls_ca_certificate=/etc/ssl/certs/ca.crt \
7
--require_client_certificates=true \
8
--auth_tokens_file=/etc/bazel/auth_tokens.json

Worker Authentication:

1
{
2
"tokens": {
3
"worker-pool-1": {
4
"token": "secure-worker-token-1",
5
"permissions": ["execute", "read_cache"],
6
"allowed_actions": [".*\\.compile", ".*\\.test"],
7
"resource_limits": {
8
"cpu": "4",
9
"memory": "8GB",
10
"disk": "100GB"
11
}
12
}
13
}
14
}

DevOps Hub API Security#

Authentication Configuration#

API Key Management:

1
# DevOps Hub API key rotation
2
CURRENT_KEY="$DEVOPS_API_KEY"
3
PROJECT_ID="your-project-id"
4
5
# Generate new API key
6
NEW_KEY=$(curl -X POST "https://console.assistance.bg/api/v2/auth/rotate-key" \
7
-H "Authorization: Bearer $CURRENT_KEY" \
8
-H "Content-Type: application/json" \
9
--data '{"project_id": "'$PROJECT_ID'"}' | \
10
jq -r '.key')
11
12
# Update runners with new key
13
echo "DEVOPS_API_KEY=$NEW_KEY" > /etc/runner/environment
14
sudo systemctl restart runner-service
15
16
echo "API key rotated successfully"

Rate Limiting Monitoring:

1
#!/bin/bash
2
# Monitor DevOps Hub API usage
3
4
API_KEY="$DEVOPS_API_KEY"
5
6
# Check current usage
7
USAGE=$(curl -s -X GET "https://console.assistance.bg/api/v2/auth/usage" \
8
-H "Authorization: Bearer $API_KEY" | \
9
jq '{requests_used: .requests_used, requests_limit: .requests_limit, reset_time: .reset_time}')
10
11
echo "Current API usage: $USAGE"
12
13
# Alert if approaching limit
14
USED=$(echo $USAGE | jq '.requests_used')
15
LIMIT=$(echo $USAGE | jq '.requests_limit')
16
THRESHOLD=$(($LIMIT * 80 / 100)) # 80% threshold
17
18
if [ "$USED" -gt "$THRESHOLD" ]; then
19
echo "WARNING: API usage at 80% of limit"
20
# Send alert to monitoring system
21
curl -X POST "$SLACK_WEBHOOK" \
22
-d "{\"text\": \"DevOps Hub API usage high: $USED/$LIMIT requests\"}"
23
fi

API Security Headers#

Request Security:

1
# Secure API request function
2
make_secure_api_call() {
3
local endpoint="$1"
4
local method="$2"
5
local data="$3"
6
7
curl -X "$method" \
8
-H "Authorization: Bearer $DEVOPS_API_KEY" \
9
-H "Content-Type: application/json" \
10
-H "User-Agent: SecureRunner/1.0" \
11
-H "X-Request-ID: $(uuidgen)" \
12
--max-time 30 \
13
--retry 3 \
14
--retry-delay 1 \
15
--fail \
16
--silent \
17
--show-error \
18
"https://console.assistance.bg/api/v2/$endpoint" \
19
--data "$data"
20
}
21
22
# Usage example
23
make_secure_api_call "projects" "GET"

Connection Security#

TLS Configuration:

1
# Validate DevOps Hub API TLS certificate
2
check_api_security() {
3
local api_host="console.assistance.bg"
4
5
# Check TLS version
6
tls_version=$(openssl s_client -connect $api_host:443 -servername $api_host 2>/dev/null | \
7
grep -E "Protocol|Cipher" | head -2)
8
9
echo "TLS Security Check for $api_host:"
10
echo "$tls_version"
11
12
# Verify certificate
13
cert_check=$(openssl s_client -connect $api_host:443 -servername $api_host -verify_return_error 2>&1)
14
15
if echo "$cert_check" | grep -q "Verify return code: 0"; then
16
echo "✓ Certificate validation passed"
17
else
18
echo "✗ Certificate validation failed"
19
exit 1
20
fi
21
}
22
23
check_api_security

Secret Management#

HashiCorp Vault Integration#

Vault Configuration for Runners:

1
# Install and configure Vault agent
2
vault auth -method=aws role=runner-role
3
4
# Configure Vault agent for secret retrieval
5
cat > /etc/vault/agent.hcl << EOF
6
pid_file = "/var/run/vault-agent.pid"
7
8
vault {
9
address = "https://vault.assistance.bg:8200"
10
retry {
11
num_retries = 5
12
}
13
}
14
15
auto_auth {
16
method "aws" {
17
mount_path = "auth/aws"
18
config = {
19
type = "iam"
20
role = "runner-role"
21
}
22
}
23
24
sink "file" {
25
config = {
26
path = "/etc/vault/token"
27
mode = 0600
28
}
29
}
30
}
31
32
template {
33
source = "/etc/vault/templates/runner-secrets.tpl"
34
destination = "/etc/runner/secrets"
35
perms = 0600
36
command = "systemctl reload runner-service"
37
}
38
EOF
39
40
# Start Vault agent
41
systemctl enable vault-agent
42
systemctl start vault-agent

Dynamic Secret Template:

1
# /etc/vault/templates/runner-secrets.tpl
2
{{- with secret "kv/data/runner-secrets" }}
3
export DEVOPS_API_KEY="{{ .Data.data.api_key }}"
4
export DATABASE_PASSWORD="{{ .Data.data.db_password }}"
5
export SIGNING_KEY="{{ .Data.data.signing_key }}"
6
{{- end }}
7
8
{{- with secret "database/creds/runner-role" }}
9
export DB_USERNAME="{{ .Data.username }}"
10
export DB_PASSWORD="{{ .Data.password }}"
11
{{- end }}

AWS Secrets Manager#

Runner Secret Retrieval:

1
#!/usr/bin/env python3
2
# Runner secret management with AWS Secrets Manager
3
4
import boto3
5
import json
6
import os
7
from botocore.exceptions import ClientError
8
9
class RunnerSecretsManager:
10
def __init__(self, region_name='us-east-1'):
11
self.secrets_client = boto3.client('secretsmanager', region_name=region_name)
12
13
def get_secret(self, secret_name):
14
"""Retrieve secret from AWS Secrets Manager"""
15
try:
16
response = self.secrets_client.get_secret_value(SecretId=secret_name)
17
return json.loads(response['SecretString'])
18
except ClientError as e:
19
print(f"Failed to retrieve secret {secret_name}: {e}")
20
return None
21
22
def update_runner_env(self, secret_name, env_file_path):
23
"""Update runner environment with secrets"""
24
secrets = self.get_secret(secret_name)
25
if not secrets:
26
return False
27
28
env_lines = []
29
for key, value in secrets.items():
30
env_lines.append(f"export {key}='{value}'")
31
32
with open(env_file_path, 'w') as f:
33
f.write('\n'.join(env_lines))
34
35
os.chmod(env_file_path, 0o600)
36
return True
37
38
# Usage
39
secrets_manager = RunnerSecretsManager()
40
secrets_manager.update_runner_env('runner-secrets', '/etc/runner/environment')

IAM Role for Runners:

1
{
2
"Version": "2012-10-17",
3
"Statement": [
4
{
5
"Effect": "Allow",
6
"Action": [
7
"secretsmanager:GetSecretValue"
8
],
9
"Resource": [
10
"arn:aws:secretsmanager:us-east-1:account:secret:runner-secrets/*"
11
],
12
"Condition": {
13
"StringEquals": {
14
"secretsmanager:VersionStage": "AWSCURRENT"
15
},
16
"DateLessThan": {
17
"aws:CurrentTime": "2025-12-31T23:59:59Z"
18
}
19
}
20
},
21
{
22
"Effect": "Allow",
23
"Action": [
24
"kms:Decrypt"
25
],
26
"Resource": [
27
"arn:aws:kms:us-east-1:account:key/key-id"
28
]
29
}
30
]
31
}

Azure Key Vault#

Service Principal Authentication:

1
# Azure Key Vault integration for runners
2
az login --service-principal \
3
--username $AZURE_CLIENT_ID \
4
--password $AZURE_CLIENT_SECRET \
5
--tenant $AZURE_TENANT_ID
6
7
# Retrieve secrets
8
get_keyvault_secret() {
9
local vault_name="$1"
10
local secret_name="$2"
11
12
az keyvault secret show \
13
--vault-name "$vault_name" \
14
--name "$secret_name" \
15
--query "value" \
16
--output tsv
17
}
18
19
# Update runner configuration
20
DEVOPS_API_KEY=$(get_keyvault_secret "runner-vault" "devops-api-key")
21
echo "export DEVOPS_API_KEY='$DEVOPS_API_KEY'" > /etc/runner/secrets
22
chmod 600 /etc/runner/secrets

Google Secret Manager#

Service Account Configuration:

1
# Authenticate with service account
2
export GOOGLE_APPLICATION_CREDENTIALS="/etc/runner/service-account.json"
3
4
# Install Google Cloud SDK
5
curl https://sdk.cloud.google.com | bash
6
source ~/.bashrc
7
8
# Retrieve secrets
9
get_gcp_secret() {
10
local project_id="$1"
11
local secret_name="$2"
12
local version="${3:-latest}"
13
14
gcloud secrets versions access "$version" \
15
--secret="$secret_name" \
16
--project="$project_id"
17
}
18
19
# Update runner environment
20
DEVOPS_API_KEY=$(get_gcp_secret "your-project" "devops-api-key")
21
echo "DEVOPS_API_KEY=$DEVOPS_API_KEY" >> /etc/runner/environment

Container Security#

Docker Security Hardening#

Secure Docker Configuration:

1
# /etc/docker/daemon.json
2
{
3
"log-driver": "json-file",
4
"log-opts": {
5
"max-size": "100m",
6
"max-file": "3"
7
},
8
"userns-remap": "dockeruser",
9
"no-new-privileges": true,
10
"seccomp-profile": "/etc/docker/seccomp.json",
11
"selinux-enabled": true,
12
"storage-driver": "overlay2",
13
"storage-opts": [
14
"overlay2.override_kernel_check=true"
15
],
16
"default-ulimits": {
17
"nproc": {
18
"Hard": 1024,
19
"Name": "nproc",
20
"Soft": 1024
21
},
22
"nofile": {
23
"Hard": 65536,
24
"Name": "nofile",
25
"Soft": 65536
26
}
27
},
28
"live-restore": true,
29
"userland-proxy": false,
30
"experimental": false
31
}

Rootless Docker Setup:

1
# Install rootless Docker
2
curl -fsSL https://get.docker.com/rootless | sh
3
4
# Configure rootless environment
5
echo 'export PATH=/home/$USER/bin:$PATH' >> ~/.bashrc
6
echo 'export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock' >> ~/.bashrc
7
8
# Start rootless Docker service
9
systemctl --user enable docker
10
systemctl --user start docker
11
12
# Verify rootless installation
13
docker context use rootless
14
docker run --rm hello-world

Container Runtime Security:

1
#!/bin/bash
2
# Secure container execution for runners
3
4
run_secure_container() {
5
local image="$1"
6
local command="$2"
7
8
docker run --rm \
9
--security-opt=no-new-privileges:true \
10
--security-opt=apparmor:docker-default \
11
--cap-drop=ALL \
12
--cap-add=CHOWN \
13
--cap-add=SETUID \
14
--cap-add=SETGID \
15
--read-only \
16
--tmpfs /tmp \
17
--tmpfs /var/tmp \
18
--network=bridge \
19
--memory=1g \
20
--cpus=1.0 \
21
--ulimit nproc=1024:1024 \
22
--ulimit nofile=1024:1024 \
23
--user 1000:1000 \
24
"$image" \
25
"$command"
26
}
27
28
# Usage
29
run_secure_container "alpine:latest" "sh -c 'echo Hello World'"

Image Security Scanning#

Trivy Integration:

1
#!/bin/bash
2
# Container image vulnerability scanning
3
4
scan_runner_images() {
5
local images=("$@")
6
7
for image in "${images[@]}"; do
8
echo "Scanning $image..."
9
10
# Scan for vulnerabilities
11
trivy image --format json --output "${image//\//_}-scan.json" "$image"
12
13
# Check for critical vulnerabilities
14
critical_count=$(jq '.Results[].Vulnerabilities | map(select(.Severity == "CRITICAL")) | length' "${image//\//_}-scan.json" 2>/dev/null || echo "0")
15
16
if [ "$critical_count" -gt 0 ]; then
17
echo "❌ CRITICAL: $image has $critical_count critical vulnerabilities"
18
exit 1
19
else
20
echo "✅ PASSED: $image has no critical vulnerabilities"
21
fi
22
done
23
}
24
25
# Scan common runner images
26
scan_runner_images \
27
"ubuntu:22.04" \
28
"node:18-alpine" \
29
"python:3.11-slim"

Cosign Image Signing:

1
# Generate signing keys
2
cosign generate-key-pair
3
4
# Sign container image
5
cosign sign --key cosign.key runner-image:latest
6
7
# Verify signed image
8
cosign verify --key cosign.pub runner-image:latest
9
10
# Policy enforcement in admission controller
11
cat > image-policy.yaml << EOF
12
apiVersion: kyverno.io/v1
13
kind: ClusterPolicy
14
metadata:
15
name: require-signed-images
16
spec:
17
validationFailureAction: enforce
18
background: false
19
rules:
20
- name: check-signature
21
match:
22
any:
23
- resources:
24
kinds:
25
- Pod
26
validate:
27
message: "Images must be signed with cosign"
28
pattern:
29
spec:
30
containers:
31
- name: "*"
32
image: "*/runner-*:*"
33
EOF

Runtime Security#

Falco Runtime Monitoring:

1
# /etc/falco/falco_rules.local.yaml
2
- rule: Suspicious Runner Activity
3
desc: Detect suspicious activities in runner containers
4
condition: >
5
container and
6
(proc.name in (wget, curl, nc, ncat, netcat) and
7
proc.args contains "reverse" or
8
proc.args contains "shell") or
9
(spawned_process and
10
proc.name in (bash, sh, zsh) and
11
proc.args contains "-c" and
12
proc.args contains "eval")
13
output: >
14
Suspicious activity in runner container
15
(user=%user.name command=%proc.cmdline container=%container.name
16
image=%container.image.repository)
17
priority: WARNING
18
tags: [runner, security, malware]
19
20
- rule: Runner Container Escape Attempt
21
desc: Detect container escape attempts
22
condition: >
23
container and
24
(proc.name in (runc, docker, kubectl) or
25
fd.name startswith /var/run/docker.sock or
26
fd.name startswith /proc/*/root)
27
output: >
28
Container escape attempt detected
29
(user=%user.name command=%proc.cmdline container=%container.name)
30
priority: CRITICAL
31
tags: [runner, escape, security]

Container Network Policies:

1
# Kubernetes NetworkPolicy for runner pods
2
apiVersion: networking.k8s.io/v1
3
kind: NetworkPolicy
4
metadata:
5
name: runner-network-policy
6
namespace: runners
7
spec:
8
podSelector:
9
matchLabels:
10
app: self-hosted-runner
11
policyTypes:
12
- Ingress
13
- Egress
14
ingress:
15
- from:
16
- namespaceSelector:
17
matchLabels:
18
name: monitoring
19
ports:
20
- protocol: TCP
21
port: 8080
22
egress:
23
- to: []
24
ports:
25
- protocol: TCP
26
port: 443 # HTTPS only
27
- protocol: UDP
28
port: 53 # DNS
29
- to:
30
- namespaceSelector:
31
matchLabels:
32
name: kube-system

Operating System Hardening#

Linux Security Configuration#

CIS Benchmark Implementation:

1
#!/bin/bash
2
# CIS Level 1 hardening for Ubuntu runner hosts
3
4
# Disable unused filesystems
5
cat > /etc/modprobe.d/blacklist-runner.conf << EOF
6
install cramfs /bin/true
7
install freevxfs /bin/true
8
install jffs2 /bin/true
9
install hfs /bin/true
10
install hfsplus /bin/true
11
install squashfs /bin/true
12
install udf /bin/true
13
install vfat /bin/true
14
EOF
15
16
# Configure secure boot parameters
17
echo 'GRUB_CMDLINE_LINUX="audit=1 audit_backlog_limit=8192"' >> /etc/default/grub
18
update-grub
19
20
# Harden kernel parameters
21
cat > /etc/sysctl.d/99-runner-security.conf << EOF
22
# IP Spoofing protection
23
net.ipv4.conf.all.rp_filter = 1
24
net.ipv4.conf.default.rp_filter = 1
25
26
# Ignore ICMP ping requests
27
net.ipv4.icmp_echo_ignore_all = 1
28
29
# Ignore send redirects
30
net.ipv4.conf.all.send_redirects = 0
31
net.ipv4.conf.default.send_redirects = 0
32
33
# Disable source packet routing
34
net.ipv4.conf.all.accept_source_route = 0
35
net.ipv6.conf.all.accept_source_route = 0
36
37
# Ignore ICMP redirects
38
net.ipv4.conf.all.accept_redirects = 0
39
net.ipv6.conf.all.accept_redirects = 0
40
41
# Ignore secure ICMP redirects
42
net.ipv4.conf.all.secure_redirects = 0
43
44
# Log Martians
45
net.ipv4.conf.all.log_martians = 1
46
47
# Enable TCP SYN Cookies
48
net.ipv4.tcp_syncookies = 1
49
50
# Disable IPv6 if not needed
51
net.ipv6.conf.all.disable_ipv6 = 1
52
net.ipv6.conf.default.disable_ipv6 = 1
53
EOF
54
55
sysctl -p /etc/sysctl.d/99-runner-security.conf

User and Permission Hardening:

1
#!/bin/bash
2
# User security for runner hosts
3
4
# Create dedicated runner user
5
useradd -r -m -s /bin/bash -d /home/runner runner
6
usermod -aG docker runner
7
8
# Set secure umask
9
echo 'umask 027' >> /home/runner/.bashrc
10
echo 'umask 027' >> /etc/profile
11
12
# Configure sudo access
13
cat > /etc/sudoers.d/runner << EOF
14
runner ALL=(ALL) NOPASSWD: /usr/bin/docker, /usr/bin/systemctl restart runner-service
15
Defaults:runner !visiblepw
16
Defaults:runner always_set_home
17
Defaults:runner match_group_by_gid
18
Defaults:runner env_reset
19
Defaults:runner env_keep += "SSH_AUTH_SOCK"
20
EOF
21
22
# Set file permissions
23
chmod 755 /home/runner
24
chmod 700 /home/runner/.ssh
25
chmod 600 /home/runner/.ssh/authorized_keys
26
chmod 600 /home/runner/.bashrc
27
28
# Remove unnecessary packages
29
apt-get remove --purge -y \
30
telnet \
31
rsh-client \
32
rsh-server \
33
ypbind \
34
ypserv \
35
tftp \
36
tftp-server \
37
talk \
38
talk-server
39
40
apt-get autoremove -y

macOS Security Configuration#

macOS Runner Hardening:

1
#!/bin/bash
2
# macOS security configuration for runners
3
4
# Enable firewall
5
sudo /usr/libexec/ApplicationFirewall/socketfilterfw --setglobalstate on
6
sudo /usr/libexec/ApplicationFirewall/socketfilterfw --setloggingmode on
7
sudo /usr/libexec/ApplicationFirewall/socketfilterfw --setstealthmode on
8
9
# Disable remote services
10
sudo launchctl disable system/com.openssh.sshd
11
sudo launchctl disable system/com.apple.screensharing
12
13
# Configure automatic updates
14
sudo softwareupdate --schedule on
15
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate AutomaticDownload -bool true
16
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate AutomaticallyInstallMacOSUpdates -bool true
17
18
# Secure system preferences
19
sudo spctl --master-enable
20
sudo defaults write /Library/Preferences/com.apple.alf globalstate -int 1
21
22
# Create runner user
23
sudo dscl . -create /Users/runner
24
sudo dscl . -create /Users/runner UserShell /bin/bash
25
sudo dscl . -create /Users/runner RealName "Runner Service Account"
26
sudo dscl . -create /Users/runner UniqueID 503
27
sudo dscl . -create /Users/runner PrimaryGroupID 20
28
sudo dscl . -create /Users/runner NFSHomeDirectory /Users/runner
29
sudo dscl . -passwd /Users/runner $(openssl rand -base64 32)
30
31
# Configure runner environment
32
sudo mkdir -p /Users/runner
33
sudo chown runner:staff /Users/runner
34
sudo chmod 700 /Users/runner

Windows Security Configuration#

Windows Runner Hardening:

1
# Windows security hardening for runners
2
# Run as Administrator
3
4
# Enable Windows Defender
5
Set-MpPreference -DisableRealtimeMonitoring $false
6
Set-MpPreference -SubmitSamplesConsent SendAllSamples
7
Set-MpPreference -MAPSReporting Advanced
8
9
# Configure Windows Firewall
10
netsh advfirewall set allprofiles state on
11
netsh advfirewall firewall add rule name="Allow HTTPS Outbound" dir=out action=allow protocol=TCP localport=443
12
netsh advfirewall firewall add rule name="Block All Inbound" dir=in action=block
13
14
# Disable unnecessary services
15
$services = @(
16
"Fax",
17
"TelnetServer",
18
"RemoteRegistry",
19
"RemoteAccess",
20
"SharedAccess"
21
)
22
23
foreach ($service in $services) {
24
Stop-Service -Name $service -Force -ErrorAction SilentlyContinue
25
Set-Service -Name $service -StartupType Disabled -ErrorAction SilentlyContinue
26
}
27
28
# Create runner service account
29
$runnerPassword = ConvertTo-SecureString -AsPlainText (New-Guid).Guid -Force
30
New-LocalUser -Name "RunnerService" -Password $runnerPassword -FullName "CI/CD Runner Service"
31
Add-LocalGroupMember -Group "Administrators" -Member "RunnerService"
32
33
# Configure audit policies
34
auditpol /set /category:"Logon/Logoff" /success:enable /failure:enable
35
auditpol /set /category:"Object Access" /success:enable /failure:enable
36
auditpol /set /category:"Process Tracking" /success:enable /failure:enable
37
auditpol /set /category:"System" /success:enable /failure:enable
38
39
# Harden registry settings
40
reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa" /v LmCompatibilityLevel /t REG_DWORD /d 5 /f
41
reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa" /v NoLMHash /t REG_DWORD /d 1 /f
42
reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa" /v DisableDomainCreds /t REG_DWORD /d 1 /f
43
44
Write-Host "Windows hardening completed"

Audit Logging#

Centralized Logging Configuration#

Rsyslog Configuration:

1
# /etc/rsyslog.d/50-runner-audit.conf
2
# Runner audit logging configuration
3
4
# Load modules
5
module(load="imfile" PollingInterval="10")
6
7
# Runner service logs
8
input(type="imfile"
9
File="/var/log/runner/service.log"
10
Tag="runner-service"
11
StateFile="runner-service-state")
12
13
# Docker audit logs
14
input(type="imfile"
15
File="/var/log/docker-audit.log"
16
Tag="docker-audit"
17
StateFile="docker-audit-state")
18
19
# API access logs
20
input(type="imfile"
21
File="/var/log/runner/api-access.log"
22
Tag="api-access"
23
StateFile="api-access-state")
24
25
# Forward to central logging
26
*.* @@logs.assistance.bg:514
27
28
# Local file backup
29
if $programname == 'runner-service' then /var/log/runner/audit.log
30
if $programname == 'docker-audit' then /var/log/security/docker.log
31
if $programname == 'api-access' then /var/log/security/api.log
32
33
# Rotate logs
34
$WorkDirectory /var/spool/rsyslog
35
$ActionQueueFileName runner-audit
36
$ActionQueueMaxDiskSpace 1g
37
$ActionQueueSaveOnShutdown on
38
$ActionQueueType LinkedList
39
$ActionResumeRetryCount -1

Audit Framework Setup:

1
#!/bin/bash
2
# Configure Linux audit framework for runners
3
4
# Install auditd
5
apt-get install -y auditd audispd-plugins
6
7
# Configure audit rules
8
cat > /etc/audit/rules.d/runner-audit.rules << EOF
9
# Delete all existing rules
10
-D
11
12
# Set buffer size
13
-b 8192
14
15
# Set failure mode
16
-f 1
17
18
# Audit file access in runner directories
19
-w /home/runner -p wa -k runner-files
20
-w /etc/runner -p wa -k runner-config
21
-w /var/lib/runner -p wa -k runner-data
22
23
# Audit Docker socket access
24
-w /var/run/docker.sock -p wa -k docker-access
25
26
# Audit system calls for privilege escalation
27
-a always,exit -F arch=b64 -S execve -k exec-monitoring
28
-a always,exit -F arch=b64 -S setuid -S setgid -S setreuid -S setregid -k privilege-escalation
29
30
# Audit network connections
31
-a always,exit -F arch=b64 -S socket -S bind -S connect -k network-access
32
33
# Audit file modifications
34
-a always,exit -F arch=b64 -S openat -S open -S creat -S truncate -S ftruncate -k file-access
35
36
# Audit authentication events
37
-w /var/log/auth.log -p wa -k authentication
38
-w /etc/passwd -p wa -k passwd-changes
39
-w /etc/group -p wa -k group-changes
40
-w /etc/shadow -p wa -k shadow-changes
41
42
# Lock configuration
43
-e 2
44
EOF
45
46
# Restart auditd
47
systemctl enable auditd
48
systemctl restart auditd
49
50
echo "Audit configuration completed"

Application Logging#

Structured Logging for Runners:

1
#!/usr/bin/env python3
2
# Runner audit logging implementation
3
4
import json
5
import logging
6
import logging.handlers
7
import time
8
from datetime import datetime
9
from typing import Dict, Any
10
11
class RunnerAuditLogger:
12
def __init__(self, log_file="/var/log/runner/audit.log"):
13
self.logger = logging.getLogger("runner-audit")
14
self.logger.setLevel(logging.INFO)
15
16
# File handler with rotation
17
handler = logging.handlers.RotatingFileHandler(
18
log_file, maxBytes=100*1024*1024, backupCount=10
19
)
20
21
# JSON formatter
22
formatter = logging.Formatter('%(message)s')
23
handler.setFormatter(formatter)
24
self.logger.addHandler(handler)
25
26
# Syslog handler for centralized logging
27
syslog_handler = logging.handlers.SysLogHandler(address='/dev/log')
28
syslog_handler.setFormatter(formatter)
29
self.logger.addHandler(syslog_handler)
30
31
def log_event(self, event_type: str, details: Dict[str, Any],
32
user_id: str = None, session_id: str = None):
33
"""Log structured audit events"""
34
audit_entry = {
35
"timestamp": datetime.utcnow().isoformat(),
36
"event_type": event_type,
37
"user_id": user_id,
38
"session_id": session_id,
39
"details": details,
40
"hostname": "runner-host",
41
"version": "1.0"
42
}
43
44
self.logger.info(json.dumps(audit_entry))
45
46
def log_api_access(self, method: str, endpoint: str, status_code: int,
47
response_time: float, user_agent: str = None):
48
"""Log API access events"""
49
self.log_event("api_access", {
50
"method": method,
51
"endpoint": endpoint,
52
"status_code": status_code,
53
"response_time_ms": response_time * 1000,
54
"user_agent": user_agent
55
})
56
57
def log_workflow_execution(self, workflow_id: str, repository: str,
58
branch: str, commit_sha: str, status: str):
59
"""Log workflow execution events"""
60
self.log_event("workflow_execution", {
61
"workflow_id": workflow_id,
62
"repository": repository,
63
"branch": branch,
64
"commit_sha": commit_sha,
65
"status": status
66
})
67
68
def log_security_event(self, event_description: str, severity: str,
69
source_ip: str = None):
70
"""Log security-related events"""
71
self.log_event("security_event", {
72
"description": event_description,
73
"severity": severity,
74
"source_ip": source_ip
75
})
76
77
# Usage example
78
if __name__ == "__main__":
79
logger = RunnerAuditLogger()
80
81
# Log API access
82
logger.log_api_access("POST", "/api/v2/projects", 201, 0.245)
83
84
# Log workflow execution
85
logger.log_workflow_execution(
86
"workflow-123", "org/repo", "main", "abc123", "completed"
87
)
88
89
# Log security event
90
logger.log_security_event(
91
"Failed authentication attempt", "HIGH", "192.168.1.100"
92
)

Compliance Logging#

SOC 2 Audit Trail:

1
#!/bin/bash
2
# SOC 2 compliance logging setup
3
4
# Create compliance log directories
5
mkdir -p /var/log/compliance/{access,changes,security}
6
chmod 750 /var/log/compliance
7
chown root:adm /var/log/compliance
8
9
# Configure compliance-specific logging
10
cat > /etc/rsyslog.d/60-compliance.conf << EOF
11
# SOC 2 Compliance Logging
12
13
# Access controls and authentication
14
auth,authpriv.* /var/log/compliance/access/auth.log
15
16
# System changes
17
*.notice;*.warn /var/log/compliance/changes/system.log
18
19
# Security events
20
local0.* /var/log/compliance/security/events.log
21
22
# Ensure log integrity
23
\$CreateDirs on
24
\$DirCreateMode 0750
25
\$FileCreateMode 0640
26
\$Umask 0022
27
EOF
28
29
# Setup log retention policy
30
cat > /etc/logrotate.d/compliance << EOF
31
/var/log/compliance/*/*.log {
32
daily
33
rotate 2555 # 7 years retention
34
compress
35
delaycompress
36
missingok
37
notifempty
38
create 640 root adm
39
postrotate
40
/bin/kill -HUP \$(cat /var/run/rsyslogd.pid 2> /dev/null) 2> /dev/null || true
41
endscript
42
}
43
EOF
44
45
# Create compliance monitoring script
46
cat > /usr/local/bin/compliance-monitor.sh << 'EOF'
47
#!/bin/bash
48
# Compliance monitoring and alerting
49
50
COMPLIANCE_LOG="/var/log/compliance/summary.log"
51
ALERT_THRESHOLD=5
52
53
# Check for security violations
54
security_events=$(grep -c "SECURITY_VIOLATION" /var/log/compliance/security/events.log 2>/dev/null || echo "0")
55
56
if [ "$security_events" -gt "$ALERT_THRESHOLD" ]; then
57
echo "$(date): HIGH: $security_events security events detected" >> "$COMPLIANCE_LOG"
58
# Send alert to SOC
59
curl -X POST "$SOC_WEBHOOK" -d "{\"alert\":\"High security events: $security_events\"}"
60
fi
61
62
# Check access control violations
63
failed_access=$(grep -c "authentication failure" /var/log/compliance/access/auth.log 2>/dev/null || echo "0")
64
65
if [ "$failed_access" -gt "$ALERT_THRESHOLD" ]; then
66
echo "$(date): MEDIUM: $failed_access failed access attempts" >> "$COMPLIANCE_LOG"
67
fi
68
69
# Generate daily compliance report
70
if [ "$(date +%H)" = "00" ]; then
71
{
72
echo "Daily Compliance Report - $(date +%Y-%m-%d)"
73
echo "=================================="
74
echo "Total security events: $security_events"
75
echo "Failed access attempts: $failed_access"
76
echo "System changes: $(grep -c "system change" /var/log/compliance/changes/system.log 2>/dev/null || echo "0")"
77
} >> "$COMPLIANCE_LOG"
78
fi
79
EOF
80
81
chmod +x /usr/local/bin/compliance-monitor.sh
82
83
# Add to crontab
84
echo "*/15 * * * * /usr/local/bin/compliance-monitor.sh" | crontab -
85
86
echo "Compliance logging configured"

Incident Response#

Security Monitoring#

Real-time Threat Detection:

1
#!/bin/bash
2
# Security monitoring and alerting for runners
3
4
# Install OSSEC HIDS for intrusion detection
5
wget -q -O - https://updates.atomicorp.com/installers/atomic | sh
6
yum install ossec-hids-server
7
8
# Configure OSSEC for runner monitoring
9
cat > /var/ossec/etc/ossec.conf << EOF
10
<ossec_config>
11
<global>
12
<email_notification>yes</email_notification>
13
<email_to>[email protected]</email_to>
14
<smtp_server>smtp.assistance.bg</smtp_server>
15
<email_from>[email protected]</email_from>
16
</global>
17
18
<rules>
19
<include>rules_config.xml</include>
20
<include>pam_rules.xml</include>
21
<include>sshd_rules.xml</include>
22
<include>telnetd_rules.xml</include>
23
<include>syslog_rules.xml</include>
24
<include>arpwatch_rules.xml</include>
25
<include>symantec-av_rules.xml</include>
26
<include>symantec-ws_rules.xml</include>
27
<include>pix_rules.xml</include>
28
<include>named_rules.xml</include>
29
<include>smbd_rules.xml</include>
30
<include>vsftpd_rules.xml</include>
31
<include>pure-ftpd_rules.xml</include>
32
<include>proftpd_rules.xml</include>
33
<include>ms_ftpd_rules.xml</include>
34
<include>ftpd_rules.xml</include>
35
<include>hordeimp_rules.xml</include>
36
<include>roundcube_rules.xml</include>
37
<include>wordpress_rules.xml</include>
38
<include>cimserver_rules.xml</include>
39
<include>vpopmail_rules.xml</include>
40
<include>vmpop3d_rules.xml</include>
41
<include>courier_rules.xml</include>
42
<include>web_rules.xml</include>
43
<include>web_appsec_rules.xml</include>
44
<include>apache_rules.xml</include>
45
<include>nginx_rules.xml</include>
46
<include>php_rules.xml</include>
47
<include>mysql_rules.xml</include>
48
<include>postgresql_rules.xml</include>
49
<include>ids_rules.xml</include>
50
<include>squid_rules.xml</include>
51
<include>firewall_rules.xml</include>
52
<include>cisco-ios_rules.xml</include>
53
<include>netscreenfw_rules.xml</include>
54
<include>sonicwall_rules.xml</include>
55
<include>postfix_rules.xml</include>
56
<include>sendmail_rules.xml</include>
57
<include>imapd_rules.xml</include>
58
<include>mailscanner_rules.xml</include>
59
<include>dovecot_rules.xml</include>
60
<include>ms-exchange_rules.xml</include>
61
<include>racoon_rules.xml</include>
62
<include>vpn_concentrator_rules.xml</include>
63
<include>spamd_rules.xml</include>
64
<include>msauth_rules.xml</include>
65
<include>mcafee_av_rules.xml</include>
66
<include>trend-osce_rules.xml</include>
67
<include>ms-se_rules.xml</include>
68
<include>zeus_rules.xml</include>
69
<include>solaris_bsm_rules.xml</include>
70
<include>vmware_rules.xml</include>
71
<include>ms_dhcp_rules.xml</include>
72
<include>asterisk_rules.xml</include>
73
<include>ossec_rules.xml</include>
74
<include>attack_rules.xml</include>
75
<include>local_rules.xml</include>
76
</rules>
77
78
<syscheck>
79
<directories check_all="yes">/home/runner</directories>
80
<directories check_all="yes">/etc/runner</directories>
81
<directories check_all="yes">/var/lib/runner</directories>
82
<directories check_all="yes">/etc/passwd</directories>
83
<directories check_all="yes">/etc/shadow</directories>
84
<directories check_all="yes">/etc/group</directories>
85
</syscheck>
86
87
<rootcheck>
88
<rootkit_files>/var/ossec/etc/shared/rootkit_files.txt</rootkit_files>
89
<rootkit_trojans>/var/ossec/etc/shared/rootkit_trojans.txt</rootkit_trojans>
90
<system_audit>/var/ossec/etc/shared/system_audit_rcl.txt</system_audit>
91
<system_audit>/var/ossec/etc/shared/cis_debian_linux_rcl.txt</system_audit>
92
<system_audit>/var/ossec/etc/shared/cis_rhel_linux_rcl.txt</system_audit>
93
<system_audit>/var/ossec/etc/shared/cis_rhel5_linux_rcl.txt</system_audit>
94
</rootcheck>
95
96
<localfile>
97
<log_format>syslog</log_format>
98
<location>/var/log/auth.log</location>
99
</localfile>
100
101
<localfile>
102
<log_format>syslog</log_format>
103
<location>/var/log/syslog</location>
104
</localfile>
105
106
<localfile>
107
<log_format>apache</log_format>
108
<location>/var/log/runner/access.log</location>
109
</localfile>
110
111
<command>
112
<name>host-deny</name>
113
<executable>host-deny.sh</executable>
114
<expect>srcip</expect>
115
<timeout_allowed>yes</timeout_allowed>
116
</command>
117
118
<command>
119
<name>firewall-drop</name>
120
<executable>firewall-drop.sh</executable>
121
<expect>srcip</expect>
122
<timeout_allowed>yes</timeout_allowed>
123
</command>
124
125
<active-response>
126
<command>host-deny</command>
127
<location>local</location>
128
<rules_id>5712</rules_id>
129
<timeout>600</timeout>
130
</active-response>
131
132
<active-response>
133
<command>firewall-drop</command>
134
<location>local</location>
135
<rules_id>5712</rules_id>
136
<timeout>600</timeout>
137
</active-response>
138
</ossec_config>
139
EOF
140
141
# Start OSSEC
142
/var/ossec/bin/ossec-control start

Custom Security Rules:

1
<!-- /var/ossec/rules/runner_rules.xml -->
2
<group name="runner,security">
3
4
<!-- Runner authentication failures -->
5
<rule id="100001" level="5">
6
<if_sid>5716</if_sid>
7
<srcip>!192.168.0.0/16</srcip>
8
<description>SSH authentication failure from external IP</description>
9
</rule>
10
11
<rule id="100002" level="10" frequency="3" timeframe="180">
12
<if_matched_sid>100001</if_matched_sid>
13
<description>Multiple SSH authentication failures from external IP</description>
14
</rule>
15
16
<!-- Suspicious runner activity -->
17
<rule id="100010" level="8">
18
<program_name>runner-service</program_name>
19
<match>unauthorized|forbidden|denied</match>
20
<description>Unauthorized access attempt to runner service</description>
21
</rule>
22
23
<!-- Container security events -->
24
<rule id="100020" level="10">
25
<program_name>docker</program_name>
26
<match>privilege|escalation|container_escape</match>
27
<description>Docker security violation detected</description>
28
</rule>
29
30
<!-- API abuse detection -->
31
<rule id="100030" level="7" frequency="10" timeframe="60">
32
<program_name>api-access</program_name>
33
<match>4[0-9][0-9]|5[0-9][0-9]</match>
34
<description>High rate of API errors detected</description>
35
</rule>
36
37
</group>

Automated Response Procedures#

Incident Response Automation:

1
#!/bin/bash
2
# Automated incident response for runner security events
3
4
INCIDENT_LOG="/var/log/security/incidents.log"
5
ALERT_WEBHOOK="$SLACK_SECURITY_WEBHOOK"
6
7
log_incident() {
8
local severity="$1"
9
local description="$2"
10
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
11
12
echo "$timestamp [$severity] $description" >> "$INCIDENT_LOG"
13
14
# Send alert for high severity incidents
15
if [ "$severity" = "CRITICAL" ] || [ "$severity" = "HIGH" ]; then
16
curl -X POST "$ALERT_WEBHOOK" \
17
-H "Content-Type: application/json" \
18
-d "{
19
\"text\": \"🚨 Security Incident\",
20
\"attachments\": [{
21
\"color\": \"danger\",
22
\"fields\": [{
23
\"title\": \"Severity\",
24
\"value\": \"$severity\",
25
\"short\": true
26
}, {
27
\"title\": \"Description\",
28
\"value\": \"$description\",
29
\"short\": false
30
}]
31
}]
32
}"
33
fi
34
}
35
36
respond_to_incident() {
37
local incident_type="$1"
38
local source_ip="$2"
39
local details="$3"
40
41
case "$incident_type" in
42
"brute_force")
43
log_incident "HIGH" "Brute force attack from $source_ip"
44
# Block IP
45
iptables -A INPUT -s "$source_ip" -j DROP
46
echo "$source_ip" >> /etc/hosts.deny
47
;;
48
49
"privilege_escalation")
50
log_incident "CRITICAL" "Privilege escalation detected: $details"
51
# Isolate runner
52
systemctl stop runner-service
53
# Preserve evidence
54
tar -czf "/var/log/security/evidence-$(date +%s).tar.gz" \
55
/var/log/runner /home/runner /etc/runner
56
;;
57
58
"container_escape")
59
log_incident "CRITICAL" "Container escape attempt: $details"
60
# Stop all containers
61
docker stop $(docker ps -q)
62
# Remove compromised containers
63
docker system prune -f
64
;;
65
66
"api_abuse")
67
log_incident "MEDIUM" "API abuse from $source_ip: $details"
68
# Rate limit IP
69
iptables -A INPUT -p tcp --dport 443 -s "$source_ip" \
70
-m limit --limit 1/minute -j ACCEPT
71
iptables -A INPUT -p tcp --dport 443 -s "$source_ip" -j DROP
72
;;
73
esac
74
}
75
76
# Monitor for security events
77
monitor_security_events() {
78
tail -F /var/log/ossec/alerts/alerts.log | while read line; do
79
if echo "$line" | grep -q "Rule: 100002"; then
80
source_ip=$(echo "$line" | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+' | head -1)
81
respond_to_incident "brute_force" "$source_ip" "SSH brute force"
82
elif echo "$line" | grep -q "privilege"; then
83
respond_to_incident "privilege_escalation" "" "$line"
84
elif echo "$line" | grep -q "container_escape"; then
85
respond_to_incident "container_escape" "" "$line"
86
fi
87
done
88
}
89
90
# Start monitoring
91
monitor_security_events &
92
echo $! > /var/run/security-monitor.pid

Breach Response Procedures#

Security Incident Playbook:

1
# incident-response-playbook.yml
2
incident_response:
3
phases:
4
preparation:
5
- Ensure incident response team contacts are current
6
- Verify communication channels are operational
7
- Confirm backup and recovery procedures are tested
8
- Validate monitoring systems are functional
9
10
identification:
11
triggers:
12
- OSSEC high severity alerts
13
- Abnormal network traffic patterns
14
- Unusual API usage patterns
15
- Failed authentication spikes
16
- Container security violations
17
18
initial_assessment:
19
- Classify incident severity (Low/Medium/High/Critical)
20
- Determine affected systems and data
21
- Estimate potential impact
22
- Activate appropriate response team
23
24
containment:
25
short_term:
26
- Isolate affected runners from network
27
- Preserve system state for forensics
28
- Change all authentication credentials
29
- Enable enhanced monitoring
30
31
long_term:
32
- Rebuild affected systems from clean images
33
- Implement additional security controls
34
- Update firewall rules
35
- Review and strengthen access controls
36
37
eradication:
38
- Remove malware or unauthorized access
39
- Patch vulnerabilities that enabled the incident
40
- Update security configurations
41
- Strengthen authentication mechanisms
42
43
recovery:
44
- Restore systems from clean backups
45
- Gradually restore services with enhanced monitoring
46
- Validate system integrity
47
- Update security baselines
48
49
lessons_learned:
50
- Conduct post-incident review
51
- Document lessons learned
52
- Update incident response procedures
53
- Implement preventive measures
54
- Update security training materials

Forensic Data Collection:

1
#!/bin/bash
2
# Forensic data collection for runner security incidents
3
4
INCIDENT_ID="INC-$(date +%Y%m%d-%H%M%S)"
5
EVIDENCE_DIR="/var/forensics/$INCIDENT_ID"
6
7
collect_evidence() {
8
echo "Starting forensic collection for incident: $INCIDENT_ID"
9
10
# Create evidence directory
11
mkdir -p "$EVIDENCE_DIR"
12
cd "$EVIDENCE_DIR"
13
14
# System information
15
echo "Collecting system information..."
16
uname -a > system_info.txt
17
ps aux > running_processes.txt
18
netstat -tulpn > network_connections.txt
19
lsof > open_files.txt
20
mount > mounted_filesystems.txt
21
df -h > disk_usage.txt
22
23
# Memory dump
24
echo "Capturing memory dump..."
25
if command -v avml &> /dev/null; then
26
avml memory_dump.lime
27
else
28
dd if=/dev/mem of=memory_dump.raw bs=1M 2>/dev/null || echo "Memory dump failed"
29
fi
30
31
# Log files
32
echo "Collecting logs..."
33
mkdir logs
34
cp -r /var/log/* logs/ 2>/dev/null
35
cp -r /var/log/runner/* logs/ 2>/dev/null
36
cp -r /var/log/docker/* logs/ 2>/dev/null
37
38
# Runner configuration
39
echo "Collecting runner configuration..."
40
mkdir config
41
cp -r /etc/runner/* config/ 2>/dev/null
42
cp /etc/passwd config/
43
cp /etc/group config/
44
cp /etc/shadow config/ 2>/dev/null
45
46
# Docker information
47
echo "Collecting Docker information..."
48
docker ps -a > docker_containers.txt
49
docker images > docker_images.txt
50
docker version > docker_version.txt
51
docker info > docker_info.txt
52
53
# Network configuration
54
echo "Collecting network configuration..."
55
ip addr show > network_interfaces.txt
56
ip route show > routing_table.txt
57
iptables -L -n -v > firewall_rules.txt
58
59
# File system timeline
60
echo "Creating file system timeline..."
61
find /home/runner -type f -exec stat {} \; > runner_file_timeline.txt 2>/dev/null
62
find /etc/runner -type f -exec stat {} \; > config_file_timeline.txt 2>/dev/null
63
64
# Hash verification
65
echo "Computing file hashes..."
66
find /home/runner /etc/runner -type f -exec md5sum {} \; > file_hashes.txt 2>/dev/null
67
68
# Create tamper-evident archive
69
echo "Creating evidence archive..."
70
cd ..
71
tar -czf "$INCIDENT_ID.tar.gz" "$INCIDENT_ID"
72
sha256sum "$INCIDENT_ID.tar.gz" > "$INCIDENT_ID.sha256"
73
74
echo "Evidence collection complete: $EVIDENCE_DIR"
75
echo "Archive: $INCIDENT_ID.tar.gz"
76
echo "Hash: $(cat $INCIDENT_ID.sha256)"
77
}
78
79
# Execute collection
80
collect_evidence
81
82
# Secure evidence
83
chmod 440 "$INCIDENT_ID.tar.gz"
84
chmod 440 "$INCIDENT_ID.sha256"
85
chown root:security "$INCIDENT_ID.tar.gz" "$INCIDENT_ID.sha256"

Compliance Standards#

SOC 2 Type II Compliance#

Control Implementation:

1
#!/bin/bash
2
# SOC 2 Type II control implementation for runners
3
4
# CC6.1 - Logical and Physical Access Controls
5
implement_access_controls() {
6
echo "Implementing SOC 2 CC6.1 controls..."
7
8
# Multi-factor authentication
9
apt-get install -y libpam-google-authenticator
10
11
cat > /etc/pam.d/sshd << EOF
12
auth required pam_google_authenticator.so
13
auth required pam_unix.so
14
account required pam_unix.so
15
session required pam_unix.so
16
EOF
17
18
# Password complexity
19
cat >> /etc/security/pwquality.conf << EOF
20
minlen = 12
21
minclass = 3
22
maxrepeat = 2
23
maxclasschars = 4
24
EOF
25
26
# Session management
27
cat >> /etc/security/limits.conf << EOF
28
* hard maxlogins 3
29
* hard maxsyslogins 10
30
EOF
31
}
32
33
# CC6.2 - Authentication and Authorization
34
implement_authentication() {
35
echo "Implementing SOC 2 CC6.2 controls..."
36
37
# Certificate-based authentication
38
mkdir -p /etc/ssh/ca
39
ssh-keygen -t ed25519 -f /etc/ssh/ca/runner_ca -C "Runner CA"
40
41
cat >> /etc/ssh/sshd_config << EOF
42
TrustedUserCAKeys /etc/ssh/ca/runner_ca.pub
43
AuthorizedPrincipalsFile /etc/ssh/authorized_principals/%u
44
ClientAliveInterval 300
45
ClientAliveCountMax 0
46
LoginGraceTime 60
47
MaxAuthTries 3
48
MaxSessions 2
49
PermitRootLogin no
50
PasswordAuthentication no
51
ChallengeResponseAuthentication yes
52
EOF
53
}
54
55
# CC6.3 - System Operations
56
implement_system_operations() {
57
echo "Implementing SOC 2 CC6.3 controls..."
58
59
# Change management tracking
60
cat > /usr/local/bin/change-tracker.sh << 'EOF'
61
#!/bin/bash
62
CHANGE_LOG="/var/log/compliance/changes.log"
63
64
track_change() {
65
local change_type="$1"
66
local description="$2"
67
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
68
local user=$(whoami)
69
70
echo "$timestamp|$user|$change_type|$description" >> "$CHANGE_LOG"
71
}
72
73
# Hook into package management
74
if [ "$1" = "install" ] || [ "$1" = "upgrade" ] || [ "$1" = "remove" ]; then
75
track_change "package" "$*"
76
fi
77
78
# Hook into configuration changes
79
if [ -f "$2" ] && [ "$1" = "edit" ]; then
80
track_change "config" "Modified $2"
81
fi
82
EOF
83
84
chmod +x /usr/local/bin/change-tracker.sh
85
}
86
87
# CC7.1 - System Monitoring
88
implement_monitoring() {
89
echo "Implementing SOC 2 CC7.1 controls..."
90
91
# Performance monitoring
92
apt-get install -y prometheus-node-exporter
93
94
cat > /etc/prometheus/node_exporter.yml << EOF
95
collectors:
96
cpu:
97
diskstats:
98
filesystem:
99
ignored-mount-points: "^/(dev|proc|sys|var/lib/docker/.+)($|/)"
100
loadavg:
101
meminfo:
102
netdev:
103
netstat:
104
systemd:
105
global:
106
scrape_interval: 15s
107
EOF
108
109
systemctl enable prometheus-node-exporter
110
systemctl start prometheus-node-exporter
111
}
112
113
# CC8.1 - Change Management
114
implement_change_management() {
115
echo "Implementing SOC 2 CC8.1 controls..."
116
117
cat > /etc/sudoers.d/change-approval << EOF
118
# Require change approval for system modifications
119
Cmnd_Alias CHANGE_COMMANDS = /usr/bin/apt, /usr/bin/systemctl, /bin/cp /etc/*, /bin/mv /etc/*
120
121
# Log all changes
122
Defaults log_host, log_year, logfile="/var/log/sudo.log"
123
EOF
124
}
125
126
# Execute all implementations
127
implement_access_controls
128
implement_authentication
129
implement_system_operations
130
implement_monitoring
131
implement_change_management
132
133
echo "SOC 2 Type II controls implemented"

ISO 27001 Information Security Management#

Risk Assessment Framework:

1
#!/usr/bin/env python3
2
# ISO 27001 risk assessment for runner infrastructure
3
4
import json
5
import datetime
6
from typing import Dict, List, Tuple
7
8
class ISO27001RiskAssessment:
9
def __init__(self):
10
self.assets = []
11
self.threats = []
12
self.vulnerabilities = []
13
self.controls = []
14
self.risk_matrix = {}
15
16
def add_asset(self, name: str, value: int, criticality: str):
17
"""Add information asset"""
18
self.assets.append({
19
'name': name,
20
'value': value,
21
'criticality': criticality,
22
'id': len(self.assets) + 1
23
})
24
25
def add_threat(self, name: str, probability: float, impact: int):
26
"""Add threat to threat register"""
27
self.threats.append({
28
'name': name,
29
'probability': probability, # 0.1 to 1.0
30
'impact': impact, # 1 to 5
31
'id': len(self.threats) + 1
32
})
33
34
def add_vulnerability(self, name: str, asset_id: int, threat_id: int,
35
exploitability: float):
36
"""Add vulnerability"""
37
self.vulnerabilities.append({
38
'name': name,
39
'asset_id': asset_id,
40
'threat_id': threat_id,
41
'exploitability': exploitability, # 0.1 to 1.0
42
'id': len(self.vulnerabilities) + 1
43
})
44
45
def add_control(self, name: str, effectiveness: float,
46
vulnerability_ids: List[int]):
47
"""Add security control"""
48
self.controls.append({
49
'name': name,
50
'effectiveness': effectiveness, # 0.1 to 1.0
51
'vulnerability_ids': vulnerability_ids,
52
'id': len(self.controls) + 1
53
})
54
55
def calculate_risk(self) -> Dict:
56
"""Calculate risk levels for all assets"""
57
risks = []
58
59
for asset in self.assets:
60
for vulnerability in self.vulnerabilities:
61
if vulnerability['asset_id'] == asset['id']:
62
threat = next(t for t in self.threats
63
if t['id'] == vulnerability['threat_id'])
64
65
# Calculate inherent risk
66
likelihood = threat['probability'] * vulnerability['exploitability']
67
impact = threat['impact'] * (asset['value'] / 5)
68
inherent_risk = likelihood * impact
69
70
# Calculate residual risk considering controls
71
risk_reduction = 0
72
for control in self.controls:
73
if vulnerability['id'] in control['vulnerability_ids']:
74
risk_reduction += control['effectiveness']
75
76
risk_reduction = min(risk_reduction, 0.95) # Max 95% reduction
77
residual_risk = inherent_risk * (1 - risk_reduction)
78
79
risks.append({
80
'asset': asset['name'],
81
'threat': threat['name'],
82
'vulnerability': vulnerability['name'],
83
'inherent_risk': round(inherent_risk, 2),
84
'residual_risk': round(residual_risk, 2),
85
'risk_level': self.get_risk_level(residual_risk)
86
})
87
88
return {'risks': risks, 'assessment_date': datetime.datetime.now().isoformat()}
89
90
def get_risk_level(self, risk_score: float) -> str:
91
"""Determine risk level based on score"""
92
if risk_score >= 4.0:
93
return "CRITICAL"
94
elif risk_score >= 3.0:
95
return "HIGH"
96
elif risk_score >= 2.0:
97
return "MEDIUM"
98
elif risk_score >= 1.0:
99
return "LOW"
100
else:
101
return "VERY LOW"
102
103
def generate_report(self) -> str:
104
"""Generate ISO 27001 risk assessment report"""
105
assessment = self.calculate_risk()
106
107
report = f"""
108
ISO 27001 Risk Assessment Report
109
Generated: {assessment['assessment_date']}
110
111
EXECUTIVE SUMMARY
112
================
113
Total Risks Identified: {len(assessment['risks'])}
114
115
Risk Distribution:
116
"""
117
118
risk_levels = {}
119
for risk in assessment['risks']:
120
level = risk['risk_level']
121
risk_levels[level] = risk_levels.get(level, 0) + 1
122
123
for level, count in sorted(risk_levels.items()):
124
report += f"- {level}: {count}\n"
125
126
report += "\nDETAILED RISK REGISTER\n=====================\n"
127
128
for risk in sorted(assessment['risks'],
129
key=lambda x: x['residual_risk'], reverse=True):
130
report += f"""
131
Asset: {risk['asset']}
132
Threat: {risk['threat']}
133
Vulnerability: {risk['vulnerability']}
134
Inherent Risk: {risk['inherent_risk']}
135
Residual Risk: {risk['residual_risk']}
136
Risk Level: {risk['risk_level']}
137
---
138
"""
139
140
return report
141
142
# Runner infrastructure risk assessment
143
if __name__ == "__main__":
144
assessment = ISO27001RiskAssessment()
145
146
# Add assets
147
assessment.add_asset("Runner Infrastructure", 4, "HIGH")
148
assessment.add_asset("API Keys and Secrets", 5, "CRITICAL")
149
assessment.add_asset("Source Code Access", 4, "HIGH")
150
assessment.add_asset("Build Artifacts", 3, "MEDIUM")
151
152
# Add threats
153
assessment.add_threat("External Hacker", 0.7, 5)
154
assessment.add_threat("Insider Threat", 0.3, 4)
155
assessment.add_threat("Supply Chain Attack", 0.5, 4)
156
assessment.add_threat("Configuration Error", 0.8, 3)
157
158
# Add vulnerabilities
159
assessment.add_vulnerability("Unpatched OS", 1, 1, 0.8)
160
assessment.add_vulnerability("Weak Authentication", 2, 1, 0.9)
161
assessment.add_vulnerability("Container Escape", 1, 1, 0.6)
162
assessment.add_vulnerability("Privilege Escalation", 1, 2, 0.7)
163
164
# Add controls
165
assessment.add_control("OS Hardening", 0.8, [1])
166
assessment.add_control("MFA Implementation", 0.9, [2])
167
assessment.add_control("Container Security", 0.7, [3])
168
assessment.add_control("Access Controls", 0.8, [4])
169
170
# Generate and print report
171
print(assessment.generate_report())

PCI DSS Compliance#

Payment Card Industry Data Security Standard:

1
#!/bin/bash
2
# PCI DSS compliance implementation for runners handling payment data
3
4
# Requirement 1: Install and maintain a firewall configuration
5
implement_pci_firewall() {
6
echo "Implementing PCI DSS Requirement 1..."
7
8
# Default deny policy
9
iptables -P INPUT DROP
10
iptables -P FORWARD DROP
11
iptables -P OUTPUT DROP
12
13
# Allow only necessary connections
14
iptables -A INPUT -i lo -j ACCEPT
15
iptables -A OUTPUT -o lo -j ACCEPT
16
17
# SSH (restricted)
18
iptables -A INPUT -p tcp --dport 22 -s 10.0.0.0/8 -j ACCEPT
19
iptables -A OUTPUT -p tcp --sport 22 -d 10.0.0.0/8 -j ACCEPT
20
21
# HTTPS for PCI compliance
22
iptables -A OUTPUT -p tcp --dport 443 -j ACCEPT
23
iptables -A INPUT -p tcp --sport 443 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
24
25
# DNS
26
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
27
iptables -A INPUT -p udp --sport 53 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
28
29
# Save configuration
30
iptables-save > /etc/iptables/rules.v4
31
}
32
33
# Requirement 2: Do not use vendor-supplied defaults
34
implement_pci_defaults() {
35
echo "Implementing PCI DSS Requirement 2..."
36
37
# Change default passwords
38
echo "runner:$(openssl rand -base64 32)" | chpasswd
39
40
# Remove default accounts
41
userdel -r ubuntu 2>/dev/null || true
42
userdel -r debian 2>/dev/null || true
43
44
# Secure SSH configuration
45
cat > /etc/ssh/sshd_config << EOF
46
Protocol 2
47
Port 22
48
PermitRootLogin no
49
PasswordAuthentication no
50
PubkeyAuthentication yes
51
AuthorizedKeysFile .ssh/authorized_keys
52
IgnoreRhosts yes
53
HostbasedAuthentication no
54
PermitEmptyPasswords no
55
ChallengeResponseAuthentication yes
56
UsePAM yes
57
X11Forwarding no
58
PrintMotd no
59
AcceptEnv LANG LC_*
60
Subsystem sftp /usr/lib/openssh/sftp-server
61
ClientAliveInterval 300
62
ClientAliveCountMax 0
63
MaxAuthTries 3
64
MaxSessions 2
65
EOF
66
67
systemctl restart sshd
68
}
69
70
# Requirement 3: Protect stored cardholder data
71
implement_pci_encryption() {
72
echo "Implementing PCI DSS Requirement 3..."
73
74
# Encrypt file systems
75
apt-get install -y cryptsetup
76
77
# Setup encrypted swap
78
swapoff -a
79
cryptsetup luksFormat /dev/disk/by-label/swap
80
cryptsetup luksOpen /dev/disk/by-label/swap swap
81
mkswap /dev/mapper/swap
82
swapon /dev/mapper/swap
83
84
# Ensure no cardholder data in logs
85
cat > /etc/rsyslog.d/99-pci-scrubbing.conf << EOF
86
# PCI DSS data scrubbing
87
\$ModLoad impcre
88
\$RepeatedMsgReduction on
89
90
# Remove credit card patterns
91
:msg, regex, "[0-9]{4}[[:space:]-]*[0-9]{4}[[:space:]-]*[0-9]{4}[[:space:]-]*[0-9]{4}" stop
92
EOF
93
94
systemctl restart rsyslog
95
}
96
97
# Requirement 4: Encrypt transmission of cardholder data
98
implement_pci_transmission() {
99
echo "Implementing PCI DSS Requirement 4..."
100
101
# Ensure only TLS 1.2+ is used
102
cat > /etc/ssl/openssl.conf << EOF
103
MinProtocol = TLSv1.2
104
CipherString = HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA
105
EOF
106
107
# Configure strong TLS for applications
108
cat > /etc/nginx/conf.d/pci-tls.conf << EOF
109
ssl_protocols TLSv1.2 TLSv1.3;
110
ssl_ciphers 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256';
111
ssl_prefer_server_ciphers on;
112
ssl_session_timeout 5m;
113
ssl_session_cache shared:SSL:10m;
114
EOF
115
}
116
117
# Execute PCI DSS implementation
118
implement_pci_firewall
119
implement_pci_defaults
120
implement_pci_encryption
121
implement_pci_transmission
122
123
echo "PCI DSS compliance controls implemented"

GDPR Data Protection#

General Data Protection Regulation compliance:

1
#!/bin/bash
2
# GDPR compliance for runner infrastructure processing EU personal data
3
4
# Data Protection Impact Assessment
5
create_dpia_framework() {
6
echo "Creating GDPR DPIA framework..."
7
8
cat > /etc/runner/dpia.json << EOF
9
{
10
"data_protection_impact_assessment": {
11
"assessment_date": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
12
"controller": "DevOps Hub",
13
"dpo_contact": "[email protected]",
14
"processing_purpose": "CI/CD automation and testing",
15
"data_categories": [
16
"developer_identities",
17
"commit_metadata",
18
"build_logs",
19
"performance_metrics"
20
],
21
"legal_basis": "legitimate_interest",
22
"data_retention": "90_days",
23
"privacy_measures": [
24
"data_minimization",
25
"pseudonymization",
26
"encryption_at_rest",
27
"encryption_in_transit",
28
"access_logging",
29
"automated_deletion"
30
]
31
}
32
}
33
EOF
34
}
35
36
# Implement privacy by design
37
implement_privacy_by_design() {
38
echo "Implementing GDPR privacy by design..."
39
40
# Data minimization - collect only necessary data
41
cat > /usr/local/bin/gdpr-data-minimizer.py << 'EOF'
42
#!/usr/bin/env python3
43
import re
44
import json
45
import sys
46
47
def minimize_log_data(log_line):
48
"""Remove or pseudonymize personal data from logs"""
49
50
# Remove email addresses
51
log_line = re.sub(r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}',
52
'[EMAIL_REMOVED]', log_line)
53
54
# Remove IP addresses (keep only subnet)
55
log_line = re.sub(r'\b(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\b',
56
r'\1.\2.XXX.XXX', log_line)
57
58
# Remove potential personal identifiers
59
log_line = re.sub(r'/users/[^/\s]+', '/users/[USER]', log_line)
60
61
return log_line
62
63
if __name__ == "__main__":
64
for line in sys.stdin:
65
print(minimize_log_data(line.strip()))
66
EOF
67
68
chmod +x /usr/local/bin/gdpr-data-minimizer.py
69
}
70
71
# Data subject rights implementation
72
implement_data_rights() {
73
echo "Implementing GDPR data subject rights..."
74
75
cat > /usr/local/bin/gdpr-rights-handler.sh << 'EOF'
76
#!/bin/bash
77
# Handle GDPR data subject rights requests
78
79
GDPR_LOG="/var/log/gdpr/rights-requests.log"
80
81
handle_access_request() {
82
local subject_id="$1"
83
local request_id="$2"
84
85
echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) - Processing access request for $subject_id (Request: $request_id)" >> "$GDPR_LOG"
86
87
# Search for personal data
88
find /var/log/runner -name "*.log" -exec grep -l "$subject_id" {} \; > "/tmp/subject_data_$request_id.txt"
89
90
# Create data export
91
mkdir -p "/var/gdpr/exports/$request_id"
92
while read -r file; do
93
grep "$subject_id" "$file" > "/var/gdpr/exports/$request_id/$(basename $file)"
94
done < "/tmp/subject_data_$request_id.txt"
95
96
# Encrypt export
97
tar -czf "/var/gdpr/exports/$request_id.tar.gz" "/var/gdpr/exports/$request_id"
98
openssl enc -aes-256-cbc -salt -in "/var/gdpr/exports/$request_id.tar.gz" \
99
-out "/var/gdpr/exports/$request_id.enc" -k "$GDPR_ENCRYPTION_KEY"
100
101
echo "Access request completed for $subject_id (Export: $request_id.enc)"
102
}
103
104
handle_erasure_request() {
105
local subject_id="$1"
106
local request_id="$2"
107
108
echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) - Processing erasure request for $subject_id (Request: $request_id)" >> "$GDPR_LOG"
109
110
# Find and remove personal data
111
find /var/log/runner -name "*.log" -exec sed -i "s/$subject_id/[ERASED]/g" {} \;
112
113
# Remove from databases
114
sqlite3 /var/lib/runner/runner.db "UPDATE builds SET author='[ERASED]' WHERE author='$subject_id'"
115
116
echo "Erasure request completed for $subject_id"
117
}
118
119
case "$1" in
120
"access")
121
handle_access_request "$2" "$3"
122
;;
123
"erasure")
124
handle_erasure_request "$2" "$3"
125
;;
126
*)
127
echo "Usage: $0 {access|erasure} subject_id request_id"
128
;;
129
esac
130
EOF
131
132
chmod +x /usr/local/bin/gdpr-rights-handler.sh
133
}
134
135
# Automated data retention and deletion
136
implement_data_retention() {
137
echo "Implementing GDPR data retention policies..."
138
139
cat > /usr/local/bin/gdpr-retention.sh << 'EOF'
140
#!/bin/bash
141
# GDPR-compliant data retention and deletion
142
143
RETENTION_DAYS=90
144
LOG_DIR="/var/log/runner"
145
GDPR_LOG="/var/log/gdpr/retention.log"
146
147
# Delete logs older than retention period
148
find "$LOG_DIR" -name "*.log" -type f -mtime +$RETENTION_DAYS -exec rm -f {} \;
149
150
# Log deletion activity
151
echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) - Automated data deletion completed (retention: ${RETENTION_DAYS} days)" >> "$GDPR_LOG"
152
153
# Secure deletion of sensitive data
154
find /tmp -name "*personal*" -type f -mmin +60 -exec shred -vfz -n 3 {} \;
155
EOF
156
157
chmod +x /usr/local/bin/gdpr-retention.sh
158
159
# Schedule retention cleanup
160
echo "0 2 * * * /usr/local/bin/gdpr-retention.sh" | crontab -
161
}
162
163
# Execute GDPR implementation
164
create_dpia_framework
165
implement_privacy_by_design
166
implement_data_rights
167
implement_data_retention
168
169
echo "GDPR compliance controls implemented"

This completes the comprehensive security hardening guide for self-hosted runners, covering all the requested topics:

  1. Security Overview - Threat models and attack vectors
  2. Network Security - VPN, firewall, and zero-trust configurations
  3. Platform-Specific Hardening - Security configs for all 5 platforms
  4. DevOps Hub API Security - Authentication, rate limiting, TLS
  5. Secret Management - Vault, AWS Secrets, Azure Key Vault, GCP
  6. Container Security - Docker hardening, image scanning, runtime security
  7. Operating System Hardening - Linux, macOS, Windows security
  8. Audit Logging - Centralized logging and compliance requirements
  9. Incident Response - Security monitoring and breach procedures
  10. Compliance Standards - SOC 2, ISO 27001, PCI DSS, GDPR

The guide provides enterprise-grade security practices that organizations can implement for compliant and secure runner deployments across all major CI/CD platforms.