Skip to main content
GZCTF integrates with various external services to provide enterprise-grade observability, storage, and communication capabilities. This guide covers all supported integrations and their configuration.

OpenTelemetry Observability

GZCTF uses OpenTelemetry for distributed tracing, metrics collection, and logging.

Configuration

Enable telemetry in appsettings.json:
{
  "Telemetry": {
    "Enable": true,
    "OpenTelemetry": {
      "Enable": true,
      "EndpointUri": "http://localhost:4317",
      "Protocol": "Grpc"  // or "HttpProtobuf"
    },
    "Prometheus": {
      "Enable": true,
      "TotalNameSuffixForCounters": true
    },
    "Console": {
      "Enable": false  // Debug only
    },
    "AzureMonitor": {
      "Enable": false,
      "ConnectionString": ""
    }
  }
}
Reference: /src/GZCTF/Extensions/Startup/TelemetryExtension.cs:26-29

Metrics Collection

GZCTF exports the following metric types:
Exported Metrics:
  • http.server.request.duration - Request latency histogram
  • http.server.active_requests - Current active requests
  • http.server.request.body.size - Request body size
  • http.server.response.body.size - Response body size
metrics.AddAspNetCoreInstrumentation();
Reference: /src/GZCTF/Extensions/Startup/TelemetryExtension.cs:40
Exported Metrics:
  • http.client.request.duration - Outbound request latency
  • http.client.request.body.size - Outbound request size
  • http.client.response.body.size - Inbound response size
Tracks all HttpClient requests (registry, webhooks, etc.)
metrics.AddHttpClientInstrumentation();
Reference: /src/GZCTF/Extensions/Startup/TelemetryExtension.cs:41
Exported Metrics:
  • process.runtime.dotnet.gc.collections.count - GC collections
  • process.runtime.dotnet.gc.heap.size - Heap size
  • process.runtime.dotnet.gc.duration - GC pause time
  • process.runtime.dotnet.thread.count - Thread count
  • process.runtime.dotnet.exception.count - Exception rate
metrics.AddRuntimeInstrumentation();
Reference: /src/GZCTF/Extensions/Startup/TelemetryExtension.cs:42
PostgreSQL Metrics:
  • npgsql.db.command.duration - Query execution time
  • npgsql.db.connection.count - Connection pool stats
  • npgsql.db.command.error.count - Query errors
metrics.AddNpgsqlInstrumentation();
Reference: /src/GZCTF/Extensions/Startup/TelemetryExtension.cs:44
Tracks S3 operations when using S3 storage backend.
metrics.AddAWSInstrumentation();
Reference: /src/GZCTF/Extensions/Startup/TelemetryExtension.cs:45
Exported Metrics:
  • health_check_status - Health check results (0=unhealthy, 1=healthy)
  • health_check_duration - Health check execution time
Includes Storage, Cache, and Database health.
metrics.AddMeter("Microsoft.Extensions.Diagnostics.HealthChecks");
Reference: /src/GZCTF/Extensions/Startup/TelemetryExtension.cs:46

Distributed Tracing

Tracing captures request flows across services:
Trace Instrumentation
tracing.AddAspNetCoreInstrumentation();        // HTTP requests
tracing.AddHttpClientInstrumentation();        // Outbound HTTP
tracing.AddEntityFrameworkCoreInstrumentation(); // Database queries
tracing.AddRedisInstrumentation();             // Cache operations
tracing.AddNpgsql();                           // PostgreSQL specific
tracing.AddAWSInstrumentation();               // S3 operations
tracing.AddGrpcClientInstrumentation();        // gRPC calls
Trace Example:
HTTP POST /api/game/42/submit
├─ DB Query: SELECT * FROM instances WHERE team_id = ?
├─ Redis GET: cache:flag:abc123
└─ HTTP POST: https://webhook.site/... (notification)
Reference: /src/GZCTF/Extensions/Startup/TelemetryExtension.cs:59-71

Prometheus Exporter

Expose metrics for Prometheus scraping:
docker-compose.yml
services:
  gzctf:
    ports:
      - "8080:8080"   # Main HTTP port
      - "3030:3030"   # Metrics port
Scrape Configuration:
prometheus.yml
scrape_configs:
  - job_name: 'gzctf'
    static_configs:
      - targets: ['gzctf:3030']
    metrics_path: '/metrics'
    scrape_interval: 15s
Health Check Endpoint:
curl http://localhost:3030/healthz

{
  "status": "Healthy",
  "totalDuration": "00:00:00.0234567",
  "entries": {
    "Storage": { "status": "Healthy" },
    "Cache": { "status": "Healthy" },  
    "Database": { "status": "Healthy" }
  }
}
Reference: /src/GZCTF/Extensions/Startup/TelemetryExtension.cs:95-102

OTLP Exporters

Send telemetry to OpenTelemetry collectors:
{
  "Telemetry": {
    "OpenTelemetry": {
      "Enable": true,
      "EndpointUri": "http://jaeger:4317",
      "Protocol": "Grpc"
    }
  }
}
Reference: /src/GZCTF/Extensions/Startup/TelemetryExtension.cs:73-90

Storage Backends

GZCTF supports multiple storage backends for challenges, writeups, and traffic captures.

Local File Storage

{
  "Storage": {
    "Type": "Local",
    "ConnectionString": "files"
  }
}
Directory Structure:
files/
├─ attachments/     # Challenge files
├─ avatars/         # User avatars
├─ capture/         # Traffic PCAP files
└─ writeups/        # Team writeups
Local storage is suitable for development and small competitions. For production, use S3 or Azure Blob.

Amazon S3 / MinIO

S3-compatible object storage for scalable file hosting:
{
  "Storage": {
    "Type": "S3",
    "ConnectionString": "S3:Endpoint=https://s3.amazonaws.com;Region=us-east-1;Bucket=gzctf-storage;AccessKey=AKIAIOSFODNN7EXAMPLE;SecretKey=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
  }
}
MinIO Configuration:
{
  "Storage": {
    "Type": "S3",
    "ConnectionString": "S3:Endpoint=http://minio:9000;Region=us-east-1;Bucket=gzctf;AccessKey=minioadmin;SecretKey=minioadmin;ForcePathStyle=true"
  }
}
S3 Bucket Policy:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::gzctf-storage/attachments/*"
    }
  ]
}
Reference: /src/GZCTF/Storage/S3BlobStorage.cs:1-385

Storage Features

All storage backends implement IBlobStorage:
Storage Operations
public interface IBlobStorage
{
    Task WriteAsync(string path, Stream content, bool append = false);
    Task<Stream> OpenReadAsync(string path);
    Task<string> ReadTextAsync(string path);
    Task DeleteAsync(string path);
    Task<bool> ExistsAsync(string path);
    Task<StorageItem> GetBlobAsync(string path);
    Task<IReadOnlyList<StorageItem>> ListAsync(string path, bool useFlatListing = false);
    Task<int> CountAsync(string path);
}
Atomic Operations:
  • S3: Uses PutObject for atomic writes
  • Local: Uses FileStream with FileMode.Create
Concurrency:
  • Thread-safe via async I/O
  • No file locking issues
  • Safe for multi-instance deployments

Email Configuration (SMTP)

GZCTF uses SMTP for account verification, password resets, and email changes.

Basic Configuration

{
  "EmailConfig": {
    "SenderAddress": "[email protected]",
    "SenderName": "CTF Platform",
    "UserName": "apikey",
    "Password": "SG.xxx",  // SendGrid API key
    "Smtp": {
      "Host": "smtp.sendgrid.net",
      "Port": 587,
      "BypassCertVerify": false
    }
  },
  "AccountPolicy": {
    "EmailConfirmationRequired": true
  }
}

Provider Examples

{
  "EmailConfig": {
    "SenderAddress": "[email protected]",
    "UserName": "apikey",
    "Password": "SG.xxxxxxxxxxxx",
    "Smtp": {
      "Host": "smtp.sendgrid.net",
      "Port": 587
    }
  }
}
{
  "EmailConfig": {
    "SenderAddress": "[email protected]",
    "UserName": "[email protected]",
    "Password": "app-specific-password",
    "Smtp": {
      "Host": "smtp.gmail.com",
      "Port": 587
    }
  }
}
Use App Passwords instead of your regular password.
{
  "EmailConfig": {
    "SenderAddress": "[email protected]",
    "UserName": "[email protected]",
    "Password": "mailgun-smtp-password",
    "Smtp": {
      "Host": "smtp.mailgun.org",
      "Port": 587
    }
  }
}
{
  "EmailConfig": {
    "SenderAddress": "[email protected]",
    "UserName": "[email protected]",
    "Password": "your-password",
    "Smtp": {
      "Host": "smtp.office365.com",
      "Port": 587
    }
  }
}

Mail Sender Architecture

Mail Queue:
  • Async background worker processes emails
  • Prevents blocking API requests
  • Automatic retry on transient failures
  • Connection pooling for efficiency
Reference: /src/GZCTF/Services/Mail/MailSender.cs:151-186

Email Templates

GZCTF uses customizable HTML email templates:
Mail Types
public enum MailType
{
    ConfirmEmail,    // Account verification
    ChangeEmail,     // Email change confirmation
    ResetPassword    // Password reset
}
Template Variables:
{title}        - Email subject
{information}  - Main message content
{btnmsg}       - Button text
{email}        - Recipient email
{userName}     - Recipient username
{url}          - Action URL (verification/reset link)
{nowtime}      - Timestamp
{platform}     - Platform name (e.g., "GZ::CTF")
Reference: /src/GZCTF/Services/Mail/MailSender.cs:84-97

Certificate Validation

SSL Certificate Handling
_smtpClient.ServerCertificateValidationCallback = (_, _, _, errors)
    => errors is SslPolicyErrors.None 
    || options.Value.Smtp?.BypassCertVerify is true;
Security: Only use BypassCertVerify: true for development with self-signed certificates. Always validate certificates in production.
Reference: /src/GZCTF/Services/Mail/MailSender.cs:52-53

TLS Configuration

For legacy SMTP servers with weak ciphers:
Cipher Suite Policy
if (!OperatingSystem.IsWindows())
{
    _smtpClient.SslCipherSuitesPolicy = new CipherSuitesPolicy(
        Enum.GetValues<TlsCipherSuite>()
            .Where(cipher =>
            {
                var cipherName = cipher.ToString();
                // Exclude MD5, SHA1, and NULL ciphers
                return !cipherName.EndsWith("MD5") 
                    && !cipherName.EndsWith("SHA") 
                    && !cipherName.EndsWith("NULL");
            })
    );
}
This allows connecting to older SMTP servers while maintaining security. Reference: /src/GZCTF/Services/Mail/MailSender.cs:40-50

Monitoring Stack Example

Docker Compose Setup

docker-compose.yml
services:
  gzctf:
    image: gztime/gzctf:latest
    environment:
      - Telemetry__Enable=true
      - Telemetry__OpenTelemetry__EndpointUri=http://otel-collector:4317
    ports:
      - "8080:8080"
      - "3030:3030"
  
  otel-collector:
    image: otel/opentelemetry-collector:latest
    command: ["--config=/etc/otel-collector-config.yaml"]
    volumes:
      - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
    ports:
      - "4317:4317"  # OTLP gRPC
      - "4318:4318"  # OTLP HTTP
  
  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"
  
  grafana:
    image: grafana/grafana:latest
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    ports:
      - "3000:3000"
    volumes:
      - grafana-data:/var/lib/grafana
  
  jaeger:
    image: jaegertracing/all-in-one:latest
    ports:
      - "16686:16686"  # Jaeger UI
      - "4317:4317"    # OTLP gRPC

volumes:
  grafana-data:

OpenTelemetry Collector Config

otel-collector-config.yaml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:
    timeout: 10s
  memory_limiter:
    check_interval: 1s
    limit_mib: 512

exporters:
  prometheus:
    endpoint: "0.0.0.0:8889"
  jaeger:
    endpoint: jaeger:14250
    tls:
      insecure: true
  logging:
    loglevel: info

service:
  pipelines:
    metrics:
      receivers: [otlp]
      processors: [batch, memory_limiter]
      exporters: [prometheus]
    traces:
      receivers: [otlp]
      processors: [batch, memory_limiter]
      exporters: [jaeger, logging]

Grafana Dashboards

Example Metrics Queries:
# Request rate
rate(http_server_request_duration_count[5m])

# P95 latency
histogram_quantile(0.95, 
  rate(http_server_request_duration_bucket[5m])
)

# Active containers
gzctf_container_count{status="running"}

# Flag submission rate
rate(gzctf_submission_total[5m])

# Database connection pool
npgsql_db_connection_count{state="used"}

Health Checks

GZCTF implements comprehensive health checks:
Health Check Registration
builder.Services.AddHealthChecks()
    .AddApplicationLifecycleHealthCheck()
    .AddCheck<StorageHealthCheck>("Storage")
    .AddCheck<CacheHealthCheck>("Cache")
    .AddCheck<DatabaseHealthCheck>("Database");
Health Check Endpoint:
curl http://localhost:3030/healthz
Kubernetes Liveness Probe:
livenessProbe:
  httpGet:
    path: /healthz
    port: 3030
  initialDelaySeconds: 30
  periodSeconds: 10
Reference: /src/GZCTF/Extensions/Startup/TelemetryExtension.cs:20-24

Next Steps

Customization

Customize themes, branding, and localization

Container Providers

Configure Docker or Kubernetes orchestration

Build docs developers (and LLMs) love