-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsetup.sh
More file actions
executable file
·451 lines (381 loc) · 19.3 KB
/
Copy pathsetup.sh
File metadata and controls
executable file
·451 lines (381 loc) · 19.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
#!/bin/bash
# setup.sh - Complete one-command setup that starts everything
set -e
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
PROJECT_ROOT="$SCRIPT_DIR"
# Load environment
if [ -f "$PROJECT_ROOT/.env" ]; then
source "$PROJECT_ROOT/.env"
else
echo "[-] .env file not found!"
exit 1
fi
GITLAB_HOST_URL="${GITLAB_HOST_URL:-http://127.0.0.1:7700}"
GITLAB_HOST_URL="${GITLAB_HOST_URL%/}"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[1;34m'
NC='\033[0m' # No Color
echo -e "${BLUE}╔════════════════════════════════════════════════════════════════╗${NC}"
echo -e "${BLUE}║ ║${NC}"
echo -e "${BLUE}║ GitLab Attack Lab - Complete Setup ║${NC}"
echo -e "${BLUE}║ ║${NC}"
echo -e "${BLUE}╚════════════════════════════════════════════════════════════════╝${NC}"
echo ""
# Step 0: Pre-pull CI job images so pipelines do not repeatedly hit Docker Hub
echo -e "${YELLOW}[STEP 0]${NC} Pre-pulling CI runner images"
CI_PREPULL_IMAGES=${CI_PREPULL_IMAGES:-"alpine:3.20 ubuntu:jammy node:20-alpine python:3.11-alpine renovate/renovate:43"}
PREPULL_FAILED=0
for IMAGE in $CI_PREPULL_IMAGES; do
echo -e " Pulling $IMAGE"
PULL_OK=0
for ATTEMPT in 1 2 3; do
if docker pull "$IMAGE" >/tmp/gitlab-setup-image-pull.log 2>&1; then
echo -e "${GREEN} ✓${NC} Pulled $IMAGE"
PULL_OK=1
break
fi
if [ "$ATTEMPT" -lt 3 ]; then
echo -e "${YELLOW} •${NC} Retry $ATTEMPT failed for $IMAGE, retrying..."
sleep 2
fi
done
if [ "$PULL_OK" -ne 1 ]; then
PREPULL_FAILED=$((PREPULL_FAILED + 1))
echo -e "${YELLOW} ⚠${NC} Could not pre-pull $IMAGE (continuing setup)"
tail -n 5 /tmp/gitlab-setup-image-pull.log || true
fi
done
if [ "$PREPULL_FAILED" -eq 0 ]; then
echo -e "${GREEN}✓${NC} CI runner images pre-pulled"
else
echo -e "${YELLOW}⚠${NC} ${PREPULL_FAILED} image(s) could not be pre-pulled; setup will continue"
fi
echo ""
# Step 1: Start Docker Compose services
echo -e "${YELLOW}[STEP 1]${NC} Starting Docker Compose services..."
cd "$PROJECT_ROOT"
if ! docker compose up -d >/tmp/gitlab-setup-compose-up.log 2>&1; then
echo -e "${RED}✗${NC} Failed to start Docker Compose services"
tail -n 50 /tmp/gitlab-setup-compose-up.log || true
exit 1
fi
echo -e "${GREEN}✓${NC} Services started"
echo ""
# Step 2: Wait for GitLab to be healthy
echo -e "${YELLOW}[STEP 2]${NC} Waiting for GitLab to initialize (this may take 5-10 minutes)..."
ATTEMPT=0
MAX_WAIT_SECONDS=1200
while true; do
ATTEMPT=$((ATTEMPT + 1))
# Try to access GitLab API - don't require container to be healthy, just accessible
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$GITLAB_HOST_URL/api/v4/version" 2>/dev/null || echo "000")
if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "401" ]; then
echo -e "${GREEN}✓${NC} GitLab is ready (took ${ATTEMPT}s)"
break
fi
# Show progress every 30 seconds
if [ $((ATTEMPT % 30)) -eq 0 ]; then
STATUS=$(docker ps --filter "name=^/gitlab-attack-lab$" --format "{{.Status}}" | head -n 1)
if [ -z "$STATUS" ]; then
STATUS="not running"
fi
echo -e " Waiting... (${ATTEMPT}s elapsed) - Container: $STATUS"
GITLAB_STATE_ERROR=$(docker inspect gitlab-attack-lab --format '{{.State.Error}}' 2>/dev/null || true)
if [ -n "$GITLAB_STATE_ERROR" ]; then
echo -e "${RED}✗${NC} GitLab container failed to start"
echo -e "${YELLOW}Docker error:${NC} $GITLAB_STATE_ERROR"
exit 1
fi
fi
if [ "$ATTEMPT" -ge "$MAX_WAIT_SECONDS" ]; then
echo -e "${RED}✗${NC} GitLab did not become reachable within ${MAX_WAIT_SECONDS}s"
echo -e "${YELLOW}Recent GitLab logs:${NC}"
docker compose logs --tail=80 gitlab || true
exit 1
fi
sleep 1
done
echo ""
# Step 3: Set root password and create admin token
echo -e "${YELLOW}[STEP 3]${NC} Setting root password and creating admin token..."
# First, set the root password and ensure admin status
echo -e " Setting password..."
docker exec gitlab-attack-lab gitlab-rails runner \
'u=User.find_by(username:"root");u.update(password:"R00t@L4b_Adm1n_2024",password_confirmation:"R00t@L4b_Adm1n_2024",admin:true);u.update(locked: false, state: :active);puts "Password set"' 2>&1 | grep -q "Password set" && echo -e "${GREEN} ✓${NC} Password set" || echo -e "${YELLOW} Note: Password may already be set${NC}"
# Wait a moment for the user to be ready
sleep 2
# Create the admin token with all scopes
echo -e " Creating admin token..."
TOKEN_OUTPUT=$(docker exec gitlab-attack-lab gitlab-rails runner \
"user = User.find_by(username: 'root'); token_name = 'lab-admin-token-' + Time.now.to_i.to_s; token = user.personal_access_tokens.create!(scopes: [:api, :read_user, :read_api, :read_repository, :write_repository, :admin_mode, :sudo], name: token_name, expires_at: 1.year.from_now); puts token.token" 2>&1)
# Extract the token (last line of output)
NEW_TOKEN=$(echo "$TOKEN_OUTPUT" | grep "glpat-" | tail -1)
if [ -n "$NEW_TOKEN" ]; then
echo -e "${GREEN} ✓${NC} Admin token created: ${NEW_TOKEN:0:20}..."
# Update .env file with the new token
if [ -f "$PROJECT_ROOT/.env" ]; then
sed -i "s/^GITLAB_ADMIN_TOKEN=.*/GITLAB_ADMIN_TOKEN=$NEW_TOKEN/" "$PROJECT_ROOT/.env"
echo -e "${GREEN} ✓${NC} Updated .env with new token"
fi
# Update the environment variable for this script
GITLAB_ADMIN_TOKEN="$NEW_TOKEN"
# Ensure the token is usable before population (GitLab can briefly return 502 after startup)
echo -e " Verifying admin token with GitLab API..."
TOKEN_READY=0
for i in $(seq 1 30); do
USER_HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -H "PRIVATE-TOKEN: $GITLAB_ADMIN_TOKEN" "$GITLAB_HOST_URL/api/v4/user" 2>/dev/null || echo "000")
if [ "$USER_HTTP_CODE" = "200" ]; then
TOKEN_READY=1
echo -e "${GREEN} ✓${NC} Admin token verified"
break
fi
sleep 2
done
if [ "$TOKEN_READY" -ne 1 ]; then
echo -e "${YELLOW} ⚠${NC} Admin token not yet accepted by API, population step will retry auth"
fi
else
echo -e "${YELLOW} ⚠${NC} Could not create new token, using existing token from .env"
fi
echo ""
# Step 3.5: Disable Auto DevOps globally
echo -e "${YELLOW}[STEP 3.5]${NC} Disabling GitLab Auto DevOps globally..."
AUTO_DEVOPS_DISABLED=0
for i in $(seq 1 20); do
HTTP_CODE=$(curl -s -o /tmp/gitlab-settings-response.json -w "%{http_code}" \
-X PUT \
-H "PRIVATE-TOKEN: $GITLAB_ADMIN_TOKEN" \
--data-urlencode "auto_devops_enabled=false" \
"$GITLAB_HOST_URL/api/v4/application/settings" 2>/dev/null || echo "000")
if [ "$HTTP_CODE" = "200" ]; then
if grep -q '"auto_devops_enabled":false' /tmp/gitlab-settings-response.json; then
AUTO_DEVOPS_DISABLED=1
echo -e "${GREEN} ✓${NC} Auto DevOps disabled globally"
break
fi
fi
sleep 2
done
if [ "$AUTO_DEVOPS_DISABLED" -ne 1 ]; then
echo -e "${YELLOW} ⚠${NC} Could not confirm Auto DevOps global disablement"
fi
echo ""
# Step 4: Populate GitLab structure from YAML
echo -e "${YELLOW}[STEP 4]${NC} Populating GitLab structure from configuration..."
cd "$PROJECT_ROOT"
# Build the setup scripts Docker image (contains Python + all dependencies)
echo -e "${YELLOW} •${NC} Building setup scripts image..."
if ! docker build -q -t gitlab-attack-lab-setup -f scripts/Dockerfile . >/tmp/gitlab-setup-image-build.log 2>&1; then
echo -e "${RED}✗${NC} Failed to build setup scripts image"
tail -n 20 /tmp/gitlab-setup-image-build.log || true
exit 1
fi
echo -e "${GREEN} ✓${NC} Setup scripts image ready"
# Helper: run a setup script inside the scripts container on the lab network.
# All Python/jq operations are performed inside this container so that the
# host only needs Docker, Docker Compose and Make.
run_setup_script() {
docker run --rm \
--network lab-network \
-v "$PROJECT_ROOT:/workspace" \
-v /tmp:/tmp \
-e GITLAB_HOST_URL="http://gitlab" \
-e GITLAB_ADMIN_TOKEN="$GITLAB_ADMIN_TOKEN" \
-e VALIDATION_TIMEOUT="${VALIDATION_TIMEOUT:-600}" \
-w /workspace \
gitlab-attack-lab-setup \
"$@"
}
# Merge scenarios into a single config
MERGED_CONFIG="/tmp/gitlab-lab-merged.yml"
echo -e "${YELLOW} •${NC} Merging scenario files..."
run_setup_script python3 scripts/merge-scenarios.py \
--base lab-config/base.yml \
--scenarios "${SCENARIOS:-lab-config/scenarios}" \
--output "$MERGED_CONFIG" 2>&1
# Run the populator and capture runner tokens
set +e
POPULATE_OUTPUT=$(run_setup_script python3 scripts/populate-gitlab.py "$MERGED_CONFIG" "http://gitlab/" "$GITLAB_ADMIN_TOKEN" 2>&1)
POPULATE_EXIT=$?
set -e
echo "$POPULATE_OUTPUT" | grep -v "^===" | grep -v "=" || true
# Check for errors and warnings in the output
ERROR_COUNT=$(echo "$POPULATE_OUTPUT" | grep -c "^\[ ERROR\]" || true)
WARN_COUNT=$(echo "$POPULATE_OUTPUT" | grep -c "^\[ WARN\]" || true)
if [ $POPULATE_EXIT -eq 0 ]; then
if [ $ERROR_COUNT -gt 0 ]; then
echo -e "${YELLOW}⚠${NC} GitLab structure populated with ${ERROR_COUNT} errors and ${WARN_COUNT} warnings"
elif [ $WARN_COUNT -gt 0 ]; then
echo -e "${YELLOW}⚠${NC} GitLab structure populated with ${WARN_COUNT} warnings"
else
echo -e "${GREEN}✓${NC} GitLab structure populated successfully"
fi
# Extract and save runner tokens to .env
if echo "$POPULATE_OUTPUT" | grep -q "=== RUNNER_TOKENS ==="; then
# First, reload .env to get updated variables
source "$PROJECT_ROOT/.env"
echo "$POPULATE_OUTPUT" | sed -n '/=== RUNNER_TOKENS ===/,/=== END_RUNNER_TOKENS ===/p' | grep '=' | grep -v "===" | while IFS='=' read -r runner_name token; do
# Convert description to env var format (e.g., shared-docker-runner -> RUNNER_TOKEN_DOCKER)
if [[ "$runner_name" == *"docker"* ]]; then
sed -i "s/^RUNNER_TOKEN_DOCKER=.*/RUNNER_TOKEN_DOCKER=$token/" "$PROJECT_ROOT/.env"
echo -e "${GREEN} ✓${NC} Saved docker runner token"
elif [[ "$runner_name" == *"shell"* ]]; then
sed -i "s/^RUNNER_TOKEN_SHELL=.*/RUNNER_TOKEN_SHELL=$token/" "$PROJECT_ROOT/.env"
echo -e "${GREEN} ✓${NC} Saved shell runner token"
fi
done
# Reload environment after updating .env
source "$PROJECT_ROOT/.env"
# Remove old runner containers to force recreation with new env vars
echo -e " Removing old runner containers..."
docker compose rm -f gitlab-runner-docker gitlab-runner-shell > /dev/null 2>&1 || true
# Recreate runner containers with new tokens from .env
echo -e " Creating runner containers with new tokens..."
if ! docker compose up -d gitlab-runner-docker gitlab-runner-shell 2>&1 | tee /tmp/runner-create.log | grep -v "^$" > /dev/null; then
echo -e "${YELLOW} ⚠${NC} Warning during runner container creation (see /tmp/runner-create.log)"
fi
# Wait for runners to be registered via GitLab API (before setting tags)
echo -e " Waiting for runners to register with GitLab..."
RUNNER_REG_WAIT=0
while [ $RUNNER_REG_WAIT -lt 30 ]; do
# Count runners by looking for "description" fields in the JSON response
RUNNER_RESPONSE=$(curl -s -H "PRIVATE-TOKEN: $GITLAB_ADMIN_TOKEN" \
"$GITLAB_HOST_URL/api/v4/runners/all?per_page=100" 2>/dev/null)
RUNNER_COUNT=$(echo "$RUNNER_RESPONSE" | grep -o '"description"' | wc -l | tr -d ' ' || echo 0)
if [ "$RUNNER_COUNT" -ge 2 ]; then
echo -e "${GREEN} ✓${NC} Runners registered with GitLab (found $RUNNER_COUNT)"
break
fi
sleep 2
RUNNER_REG_WAIT=$((RUNNER_REG_WAIT + 2))
done
# Set runner tags via API (required in GitLab 19.1+)
echo -e " Setting runner tags via API..."
run_setup_script python3 scripts/set-runner-tags.py
# Wait for runners to register with expected tags (up to 60 seconds)
echo -e " Waiting for runners to register with expected tags..."
RUNNER_WAIT=0
RUNNERS_FOUND=0
while [ $RUNNER_WAIT -lt 60 ]; do
RUNNER_CHECK=$(run_setup_script python3 scripts/check-runner-status.py)
if [[ "$RUNNER_CHECK" == ready* ]]; then
REGISTERED=$(echo "$RUNNER_CHECK" | sed -n 's/.*online=\([0-9]\+\).*/\1/p')
[ -z "$REGISTERED" ] && REGISTERED="2"
echo -e "${GREEN} ✓${NC} Runners are online and tag-matched (${REGISTERED} online)"
RUNNERS_FOUND=1
break
fi
sleep 2
RUNNER_WAIT=$((RUNNER_WAIT + 2))
done
if [ "$RUNNERS_FOUND" -ne 1 ]; then
echo -e "${YELLOW} ⚠${NC} Runner readiness check did not pass: ${RUNNER_CHECK}"
echo -e "${YELLOW} Tagged jobs may stay pending until runner registration completes${NC}"
fi
fi
else
echo -e "${RED}✗${NC} Failed to populate GitLab structure"
exit 1
fi
echo ""
# Step 5: Validate runners and scenario readiness
echo -e "${YELLOW}[STEP 5]${NC} Validating runners and scenario readiness..."
echo -e "${YELLOW} (This may take several minutes as jobs execute)${NC}"
echo ""
if ! run_setup_script python3 scripts/validate-runners.py; then
echo ""
echo -e "${YELLOW}⚠${NC} Validation incomplete or timed out"
echo -e "${YELLOW} Some scenarios may not be ready yet. You can:"
echo -e "${YELLOW} 1. Wait a few minutes and try again (jobs may still be running)"
echo -e "${YELLOW} 2. Check job status at: $GITLAB_HOST_URL/dashboard/projects"
echo -e "${YELLOW} 3. Rerun: make setup${NC}"
fi
echo ""
# Step 6: Configure pentester container
echo -e "${YELLOW}[STEP 6]${NC} Configuring pentester container..."
# Wait for pentester container to be running (up to 30 seconds)
PENTESTER_WAIT=0
while [ $PENTESTER_WAIT -lt 30 ]; do
if docker exec pentester true 2>/dev/null; then
break
fi
sleep 1
PENTESTER_WAIT=$((PENTESTER_WAIT + 1))
done
if ! docker exec pentester true 2>/dev/null; then
echo -e "${YELLOW}⚠${NC} Pentester container not responding, skipping configuration"
echo -e "${YELLOW} You may need to manually run: docker compose logs pentester${NC}"
else
# Create a dedicated pentester token for player workflows
echo -e " Creating pentester API token..."
NEW_PENTESTER_TOKEN=$(run_setup_script python3 scripts/create-pentester-token.py)
ACTIVE_PENTESTER_TOKEN=""
if [ -n "$NEW_PENTESTER_TOKEN" ]; then
ACTIVE_PENTESTER_TOKEN="$NEW_PENTESTER_TOKEN"
if grep -q '^PENTESTER_TOKEN=' "$PROJECT_ROOT/.env"; then
sed -i "s/^PENTESTER_TOKEN=.*/PENTESTER_TOKEN=$ACTIVE_PENTESTER_TOKEN/" "$PROJECT_ROOT/.env"
else
echo "PENTESTER_TOKEN=$ACTIVE_PENTESTER_TOKEN" >> "$PROJECT_ROOT/.env"
fi
echo -e "${GREEN} ✓${NC} Created pentester token and saved to .env"
elif [ -n "$PENTESTER_TOKEN" ]; then
ACTIVE_PENTESTER_TOKEN="$PENTESTER_TOKEN"
echo -e "${YELLOW} ⚠${NC} Could not mint a new pentester token, reusing existing PENTESTER_TOKEN"
else
ACTIVE_PENTESTER_TOKEN="$GITLAB_ADMIN_TOKEN"
echo -e "${YELLOW} ⚠${NC} Could not mint pentester token, falling back to admin token for tooling"
fi
# Create pipeleek config directory
docker exec pentester mkdir -p /root/.config/pipeleek 2>/dev/null || true
# Create pipeleek config with runtime token (using docker exec directly)
# Note: Use /root/.config/pipeleek/pipeleek.yaml as this is what pipeleek expects
docker exec -T pentester bash -c "echo 'gitlab:' > /root/.config/pipeleek/pipeleek.yaml && echo ' url: http://gitlab' >> /root/.config/pipeleek/pipeleek.yaml && echo \" token: $ACTIVE_PENTESTER_TOKEN\" >> /root/.config/pipeleek/pipeleek.yaml" 2>/dev/null || true
# Set proper permissions
docker exec pentester chmod 600 /root/.config/pipeleek/pipeleek.yaml 2>/dev/null || true
# Update bashrc with credentials (no banner - shown by make pentester-shell)
docker exec pentester bash -c "cat >> ~/.bashrc << 'BASHRC'
# GitLab Lab Credentials
export GITLAB_URL=\"http://gitlab\"
export PENTESTER_USER=\"pentester\"
export PENTESTER_PASSWORD=\"SecureP3nt3st3r@2024!\"
BASHRC" 2>/dev/null || true
echo -e "${GREEN}✓${NC} Pentester container configured with pipeleek"
fi
echo ""
# Final summary
if [ $ERROR_COUNT -eq 0 ] && [ $WARN_COUNT -eq 0 ]; then
echo -e "${BLUE}╔════════════════════════════════════════════════════════════════╗${NC}"
echo -e "${BLUE}║ Setup Complete! 🎉 ║${NC}"
echo -e "${BLUE}╚════════════════════════════════════════════════════════════════╝${NC}"
echo ""
echo -e "${GREEN}✓ All services running${NC}"
echo -e "${GREEN}✓ GitLab populated with lab data${NC}"
echo -e "${GREEN}✓ Pentester container ready${NC}"
else
echo -e "${YELLOW}╔════════════════════════════════════════════════════════════════╗${NC}"
echo -e "${YELLOW}║ Setup Complete with Warnings ⚠ ║${NC}"
echo -e "${YELLOW}╚════════════════════════════════════════════════════════════════╝${NC}"
echo ""
echo -e "${GREEN}✓ All services running${NC}"
if [ $ERROR_COUNT -gt 0 ]; then
echo -e "${YELLOW}⚠ GitLab populated with ${ERROR_COUNT} errors and ${WARN_COUNT} warnings${NC}"
else
echo -e "${YELLOW}⚠ GitLab populated with ${WARN_COUNT} warnings${NC}"
fi
echo -e "${GREEN}✓ Pentester container ready${NC}"
fi
# Option to enter pentester shell (only if container is running)
if docker exec pentester true 2>/dev/null; then
read -p "Enter pentester container now? (y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo -e "${BLUE}Entering pentester container...${NC}"
docker compose exec -it pentester /bin/bash
fi
else
echo -e "${YELLOW}⚠ Pentester container is not running.${NC}"
echo " You can enter it later with: docker compose exec -it pentester /bin/bash"
fi