Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions .github/workflows/cf_adblock.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
name: Monthly Cloudflare Adblock Update

on:
workflow_dispatch: # Allows manual triggering
schedule:
- cron: "0 0 1 * *" # Runs at 00:00 UTC on the 1st day of every month

env:
TF_VAR_branch_env: prod

permissions:
contents: read
id-token: write

jobs:
update_cf_adblock:
runs-on: ubuntu-latest
container:
image: ghcr.io/karteekiitg/k8s_setup:latest

steps:
- name: Checkout repository
id: checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683

- name: Load .env file to environment
shell: bash
run: |
if [ -f "./.env" ]; then
echo "Sourcing .env file..."
grep -v '^[[:space:]]*#' ./.env | grep -v '^[[:space:]]*$' | grep '=' >> $GITHUB_ENV
echo "Finished processing .env file for GITHUB_ENV."
else
echo -e "\033[31mError: .env file not found at ./.\033[0m"
exit 1
fi

- name: Load secrets to environment
shell: bash
env: # Environment variables specific to THIS step
TF_VAR_infisical_client_secret: ${{ secrets.INFISICAL_CLIENT_SECRET }}
run: |
echo "Making setup_infisical.sh executable..."
chmod +x ./.devcontainer/setup_infisical.sh
echo "Running setup_infisical.sh..."
./.devcontainer/setup_infisical.sh
if [ $? -ne 0 ]; then
echo -e "\033[31mError: setup_infisical.sh failed. See script output above for details.\033[0m"
exit 1
fi

EXPORT_FILE="$HOME/.infisical_exports.env"

if [ -f "$EXPORT_FILE" ]; then
echo "Sourcing secrets from $EXPORT_FILE to GITHUB_ENV (filtering, handling 'export' prefix, and stripping quotes)..."

# Pre-filter with grep to remove comments and truly empty lines, ensure '=' exists
# Then pipe into the while loop for further processing
grep -v '^[[:space:]]*#' "$EXPORT_FILE" | grep -v '^[[:space:]]*$' | grep '=' | \
while IFS= read -r line || [ -n "$line" ]; do # Read whole line
# Remove "export " prefix if it exists from the already filtered line
line_no_export="${line#export }"

# At this point, 'line_no_export' should be in KEY=VALUE format
# (possibly with quotes around VALUE) because of the preceding grep filters.
# We still split to handle the value quoting.

key="${line_no_export%%=*}"
value_with_potential_quotes="${line_no_export#*=}"

# Remove leading/trailing single quotes from value_with_potential_quotes
value_cleaned="${value_with_potential_quotes#\'}"
value_cleaned="${value_cleaned%\'}"
# Remove leading/trailing double quotes from value_with_potential_quotes
value_cleaned="${value_cleaned#\"}"
value_cleaned="${value_cleaned%\"}"

echo "$key=$value_cleaned" >> $GITHUB_ENV
done

echo "Finished processing $EXPORT_FILE for GITHUB_ENV."
echo "Removing $EXPORT_FILE..."
rm -f "$EXPORT_FILE"
else
echo -e "\033[31mError: Secrets export file ($EXPORT_FILE) was not found after running setup_infisical.sh.\033[0m"
exit 1
fi
echo "Secrets loaded and temporary file removed."

- name: Run Adblock List Chunking Script
run: bash chunk_adblock_lists.sh 1000 90
working-directory: ./tofu/cloudflare/adblock

- name: OpenTofu Init for cf-adblock
run: tofu init
working-directory: ./tofu/cloudflare/adblock

- name: OpenTofu Apply for cf-adblock
id: apply_cf_adblock
shell: bash
run: tofu apply -auto-approve
working-directory: ./tofu/cloudflare/adblock

- name: Install Python dependencies
shell: bash
run: |
echo "Installing cloudflare Python library..."
pip3 install cloudflare

- name: Run Cloudflare Adblock Management Script
shell: bash
run: |
echo "Running Python script manage_cloudflare_adblock.py..."
python3 manage_cloudflare_adblock.py 1000 90
working-directory: ./tofu/cloudflare/adblock # Runs Python script from the same dir as chunker & TF
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,5 @@ override.tf.json

*.pem
*.crt

processed_adblock_chunks
45 changes: 45 additions & 0 deletions tofu/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# OpenTofu Infrastructure

This directory contains all the OpenTofu modules for managing the infrastructure of this homelab. The modules are designed to be applied in a specific order to ensure dependencies are met.

## Execution Order & Workflow

It is crucial to apply these modules in the following sequence:

### 1. Remote State Backend (Optional but Recommended)

Setting up a remote backend is the first step for managing state collaboratively and securely. If you do not set up a remote backend, OpenTofu will use a local state file by default. The `kubernetes` module contains samples for local(default), GCS, and R2 backends that can be adapted for other modules.

Choose one of the following options if you want to use remote state:

- **Cloudflare R2**: See the instructions in [`./remote-state/cf/README.md`](./remote-state/cf/README.md)
- **Google Cloud Storage (GCS)**: See the instructions in [`./remote-state/gcs/README.md`](./remote-state/gcs/README.md)

### 2. Cloudflare Account Tokens

**This is a prerequisite for all other Cloudflare modules.**

This module creates the scoped API tokens that are required to authenticate and authorize the other Cloudflare-related modules.

- **Instructions**: See [`./cloudflare/account-tokens/README.md`](./cloudflare/account-tokens/README.md)

### 3. Other Cloudlare Modules

Once the prerequisites are met, you can apply the other modules as needed. By default, all modules below are configured to use the Cloudflare R2 remote state backend. If you want to use a different remote backend, you will need to adjust the `backend.tofu` file in each module accordingly (for reference check [`Kubernetes`](./kubernetes/README.md)).

- **Cloudflare Adblock**: Manages Cloudflare Zero Trust Gateway DNS policies for ad and malware blocking.
- **Instructions**: See [`./cloudflare/adblock/README.md`](./cloudflare/adblock/README.md)

- **Cloudflare Email Alias**: Configures a powerful and secure email forwarding system using Cloudflare Email Routing and a custom worker.
- **Instructions**: See [`./cloudflare/email-alias/README.md`](./cloudflare/email-alias/README.md)

### 4. Kubernetes

- **Kubernetes**: Provisions the Kubernetes cluster on proxmox using talos. This module is flexible and supports local state, as well as GCS and R2 remote backends. You can find sample backend configurations within its directory.
- **Instructions**: See [`./kubernetes/README.md`](./kubernetes/README.md)

### Suggestions (Opinionated)

I suggest you to use R2 remote state backend. It is the default for all new modules, except kubernetes. As most of us use cloudflare anyway, and has a generous 10GB free tier, I feel its a good default.

I also suggest using infisical. It is opensource and has a very generous free tier. All new modules, except Kubernetes, are configured to use infisical for secrets management by default. For Kubernetes module, infisical is optional, but recommended.
49 changes: 49 additions & 0 deletions tofu/cloudflare/account-tokens/DOCS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
## Overview

This OpenTofu module automates the creation of scoped Cloudflare Account API Tokens. By generating specific, permission-limited tokens, it enhances security by adhering to the principle of least privilege. Instead of using a master API token for all operations, other OpenTofu modules or CI/CD pipelines can use these tokens which only have the permissions necessary for their specific tasks.

## Key Resources

- **`cloudflare_account_token`**: This is the primary resource used to create the API tokens. Two distinct tokens are generated:
- **`zero_trust_tofu_token`**: This token is granted a combination of permissions that allow it to manage Cloudflare Zero Trust configurations and DNS records. This is ideal for tasks like updating ad-block lists or other DNS-based filtering.
- **`email_tofu_token`**: This token is configured with permissions to manage email routing rules and associated Workers. This is useful for automating email forwarding or processing.

- **`infisical_secret`**: For each token created, a corresponding secret is created in Infisical. This allows for the secure storage and retrieval of the token values. The secrets are named with a `TF_VAR_` prefix, making them easily consumable as environment variables in other OpenTofu configurations or scripts.

- **`cloudflare_account_permission_groups` data source**: This data source is used to dynamically fetch the available permission groups from the Cloudflare API. This avoids hardcoding permission group IDs, making the configuration more robust and adaptable to changes in the Cloudflare API.

## Instructions

### Prerequisites

Before applying this module, you must have the following in place:

1. **Configured Remote State**: A remote backend (in this case, Cloudflare R2) must be fully configured and operational. The `backend.tf` file in this module is already configured to use the R2 bucket.
2. **Infisical Project**: An Infisical project must exist, and you must have the necessary credentials (client ID, client secret, project ID) to authenticate and write secrets.
3. **Environment Variables**: The following environment variables must be set in your execution environment (e.g., your devcontainer's `.env` file):
- `TF_VAR_cloudflare_account_id` - set it in infisical manually
- `TF_VAR_cloudflare_master_account_api_token` - set it in infisical manually
- `TF_VAR_cloudflare_r2_tofu_access_key` - automatically set in the devcontainer by [cloudflare remote state](../../remote-state/cf/README.md).
- `TF_VAR_cloudflare_r2_tofu_access_secret` - automatically set in the devcontainer by [cloudflare remote state](../../remote-state/cf/README.md).
- `TF_VAR_bucket_name` - automatically set in the devcontainer when set in the `.env` file in the root folder.
- `TF_VAR_branch_env`- automatically set in the devcontainer base on the current branch.
- `TF_VAR_tofu_encryption_passphrase` - set it in infisical manually
- `TF_VAR_infisical_domain` - automatically set in the devcontainer when set in the `.env` file in the root folder.
- `TF_VAR_infisical_client_id` - automatically set in the devcontainer when set in the `.env` file in the root folder.
- `TF_VAR_infisical_client_secret` - set it in [devcontainer](/.devcontainer/README.md) manually.
- `TF_VAR_infisical_project_id` - automatically set in the devcontainer when set in the `.env` file in the root folder.
- Note: You might need to run `source ~/.zshrc` in your devcontainer to ensure the environment variables are loaded correctly after they are automatically set up in Infisical for the first time by remote state.

### Execution

Once the prerequisites are met, you can apply the configuration:

```bash
# Initialize tofu
tofu init

# Run tofu apply to create the tokens and secrets
tofu apply
```

After a successful apply, the generated tokens will be securely stored in your Infisical project under the path specified by `var.infisical_rw_secrets_path`.
14 changes: 14 additions & 0 deletions tofu/cloudflare/account-tokens/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
## Overview

This OpenTofu module is responsible for creating and managing specific, scoped Cloudflare Account API Tokens. These tokens are designed for use in other OpenTofu modules and CI/CD pipelines to perform automated tasks within the Cloudflare ecosystem.

## Key Resources

- **Scoped API Tokens**: Creates dedicated tokens for:
- **Zero Trust & DNS**: For programmatically managing Zero Trust policies, lists, and DNS records.
- **Email & Workers**: For automating email routing rules and related Worker scripts.
- **Secrets Management with Infisical**: Securely stores the generated API tokens in a specified Infisical project and path for other modules and services to consume.

## Instructions

This module assumes a pre-existing and configured OpenTofu remote state backend. For detailed prerequisites and step-by-step instructions, please refer to [DOCS.md](./DOCS.md).
18 changes: 18 additions & 0 deletions tofu/cloudflare/account-tokens/backend.tofu
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
terraform {
backend "s3" {
bucket = var.bucket_name
key = "cloudflare/account-tokens/${var.branch_env}/terraform.tfstate"
region = "auto"
skip_credentials_validation = true
skip_metadata_api_check = true
skip_region_validation = true
skip_requesting_account_id = true
skip_s3_checksum = true
use_path_style = true
endpoints = {
s3 = "https://${var.cloudflare_account_id}.r2.cloudflarestorage.com"
}
access_key = var.cloudflare_r2_tofu_access_key
secret_key = var.cloudflare_r2_tofu_access_secret
}
}
138 changes: 138 additions & 0 deletions tofu/cloudflare/account-tokens/cf.tofu
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
locals {
# Create a nested map: scope -> permission_name -> id
api_token_permission_groups_map = {
account = {
for perm in data.cloudflare_account_api_token_permission_groups_list.all.result :
perm.name => perm.id
if contains(perm.scopes, "com.cloudflare.api.account")
}
zone = {
for perm in data.cloudflare_account_api_token_permission_groups_list.all.result :
perm.name => perm.id
if contains(perm.scopes, "com.cloudflare.api.account.zone")
}
# Add R2 scope mapping
r2 = {
for perm in data.cloudflare_account_api_token_permission_groups_list.all.result :
perm.name => perm.id
if contains(perm.scopes, "com.cloudflare.edge.r2.bucket")
}
}
}

# Get API token permission groups data
data "cloudflare_account_api_token_permission_groups_list" "all" {
account_id = var.cloudflare_account_id
}

# Create Account token for Zero Trust access with proper permissions
resource "cloudflare_account_token" "zero_trust_tofu_token" {
name = "Zero Trust Tofu Token"
account_id = var.cloudflare_account_id

policies = [{
effect = "allow"
permission_groups = [{
id = local.api_token_permission_groups_map.account["Zero Trust Write"]
}]
resources = {
"com.cloudflare.api.account.${var.cloudflare_account_id}" = "*"
}
}, {
effect = "allow"
permission_groups = [{
id = local.api_token_permission_groups_map.account["Cloudflare Zero Trust Secure DNS Locations Write"]
}]
resources = {
"com.cloudflare.api.account.${var.cloudflare_account_id}" = "*"
}
}, {
effect = "allow"
permission_groups = [{
id = local.api_token_permission_groups_map.zone["DNS Write"]
}]
resources = {
"com.cloudflare.api.account.zone.${var.cloudflare_zone_id}" = "*"
}
}]
}

# Create Account token for Email and Workers access with proper permissions
resource "cloudflare_account_token" "email_tofu_token" {
name = "Email Tofu Token"
account_id = var.cloudflare_account_id

policies = [{
effect = "allow"
permission_groups = [{
id = local.api_token_permission_groups_map.account["Email Routing Addresses Write"]
}]
resources = {
"com.cloudflare.api.account.${var.cloudflare_account_id}" = "*"
}
}, {
effect = "allow"
permission_groups = [{
id = local.api_token_permission_groups_map.zone["Email Routing Rules Write"]
}]
resources = {
"com.cloudflare.api.account.zone.${var.cloudflare_zone_id}" = "*"
}
}, {
effect = "allow"
permission_groups = [{
id = local.api_token_permission_groups_map.account["Workers Scripts Write"]
}]
resources = {
"com.cloudflare.api.account.${var.cloudflare_account_id}" = "*"
}
}]
}

# Create Account token for Github Actions Workers Deployments access with proper permissions
resource "cloudflare_account_token" "gha_workers_deployment" {
name = "GitHub Actions Workers Deployment Token"
account_id = var.cloudflare_account_id

policies = [{
effect = "allow"
permission_groups = [{
id = local.api_token_permission_groups_map.account["Workers Scripts Write"]
}]
resources = {
"com.cloudflare.api.account.${var.cloudflare_account_id}" = "*"
}
}, {
effect = "allow"
permission_groups = [{
id = local.api_token_permission_groups_map.account["Account Settings Read"]
}]
resources = {
"com.cloudflare.api.account.${var.cloudflare_account_id}" = "*"
}
}, {
effect = "allow"
permission_groups = [{
id = local.api_token_permission_groups_map.zone["Workers Routes Write"] # This is what you're missing!
}]
resources = {
"com.cloudflare.api.account.zone.${var.cloudflare_zone_id}" = "*"
}
}, {
effect = "allow"
permission_groups = [{
id = local.api_token_permission_groups_map.zone["Zone Read"]
}]
resources = {
"com.cloudflare.api.account.zone.${var.cloudflare_zone_id}" = "*"
}
}, {
effect = "allow"
permission_groups = [{
id = local.api_token_permission_groups_map.zone["Zone Settings Read"]
}]
resources = {
"com.cloudflare.api.account.zone.${var.cloudflare_zone_id}" = "*"
}
}]
}
Loading