Add Pulumi for tailnet IaC management
- Manage tail8d86e.ts.net ACLs, tags, and DNS via Pulumi + Python - State stored in Pulumi Cloud (free tier) to avoid circular dependency - OAuth authentication via 1Password for secure credential management - mise tasks: tailnet-preview, tailnet-up Two-layer approach: - Layer 1 (Pulumi): Tailnet-wide config (ACLs, tags, DNS) - Layer 2 (Ansible): Node-local tailscale serve config (unchanged) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
72c2dd7096
commit
63e99998dd
9 changed files with 171 additions and 0 deletions
11
mise-tasks/tailnet-preview
Executable file
11
mise-tasks/tailnet-preview
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
#!/usr/bin/env bash
|
||||
#MISE description="Preview tailnet changes with Pulumi"
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
export TAILSCALE_OAUTH_CLIENT_ID=$(op --vault vg6xf6vvfmoh5hqjjhlhbeoaie item get wi6bkf7bcccwfy4eu776ab4p4u --fields client_id)
|
||||
export TAILSCALE_OAUTH_CLIENT_SECRET=$(op --vault vg6xf6vvfmoh5hqjjhlhbeoaie item get wi6bkf7bcccwfy4eu776ab4p4u --fields client_secret --reveal)
|
||||
export TAILSCALE_TAILNET="tail8d86e.ts.net"
|
||||
|
||||
cd "$(dirname "$0")/../pulumi"
|
||||
pulumi preview "$@"
|
||||
11
mise-tasks/tailnet-up
Executable file
11
mise-tasks/tailnet-up
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
#!/usr/bin/env bash
|
||||
#MISE description="Apply tailnet changes with Pulumi"
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
export TAILSCALE_OAUTH_CLIENT_ID=$(op --vault vg6xf6vvfmoh5hqjjhlhbeoaie item get wi6bkf7bcccwfy4eu776ab4p4u --fields client_id)
|
||||
export TAILSCALE_OAUTH_CLIENT_SECRET=$(op --vault vg6xf6vvfmoh5hqjjhlhbeoaie item get wi6bkf7bcccwfy4eu776ab4p4u --fields client_secret --reveal)
|
||||
export TAILSCALE_TAILNET="tail8d86e.ts.net"
|
||||
|
||||
cd "$(dirname "$0")/../pulumi"
|
||||
pulumi up "$@"
|
||||
|
|
@ -1,2 +1,3 @@
|
|||
[tools]
|
||||
"pipx:ansible-core" = { version = "latest", uvx = "true", uvx_args = "--with botocore --with boto3" }
|
||||
pulumi = "latest"
|
||||
|
|
|
|||
10
pulumi/.gitignore
vendored
Normal file
10
pulumi/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# Python
|
||||
.venv/
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
|
||||
# uv
|
||||
uv.lock
|
||||
|
||||
# Pulumi
|
||||
*.pyc
|
||||
2
pulumi/Pulumi.tail8d86e.yaml
Normal file
2
pulumi/Pulumi.tail8d86e.yaml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
config:
|
||||
tailscale:tailnet: tail8d86e.ts.net
|
||||
6
pulumi/Pulumi.yaml
Normal file
6
pulumi/Pulumi.yaml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
name: blumeops-tailnet
|
||||
runtime:
|
||||
name: python
|
||||
options:
|
||||
toolchain: uv
|
||||
description: Tailnet configuration for tail8d86e.ts.net
|
||||
18
pulumi/__main__.py
Normal file
18
pulumi/__main__.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
"""Pulumi program to manage tail8d86e.ts.net tailnet configuration."""
|
||||
|
||||
import pulumi
|
||||
import pulumi_tailscale as tailscale
|
||||
from pathlib import Path
|
||||
|
||||
# Read the HuJSON policy file
|
||||
policy_path = Path(__file__).parent / "policy.hujson"
|
||||
policy_content = policy_path.read_text()
|
||||
|
||||
# Manage the ACL - this completely overwrites the tailnet's ACL policy
|
||||
acl = tailscale.Acl(
|
||||
"tailnet-acl",
|
||||
acl=policy_content,
|
||||
)
|
||||
|
||||
# Export useful info
|
||||
pulumi.export("acl_id", acl.id)
|
||||
104
pulumi/policy.hujson
Normal file
104
pulumi/policy.hujson
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
// Example/default ACLs for unrestricted connections.
|
||||
{
|
||||
// Declare static groups of users. Use autogroups for all users or users with a specific role.
|
||||
// "groups": {
|
||||
// "group:example": ["alice@example.com", "bob@example.com"],
|
||||
// },
|
||||
|
||||
// Define the tags which can be applied to devices and by which users.
|
||||
// "tagOwners": {
|
||||
// "tag:example": ["autogroup:admin"],
|
||||
// },
|
||||
|
||||
// Define grants that govern access for users, groups, autogroups, tags,
|
||||
// Tailscale IP addresses, and subnet ranges.
|
||||
"grants": [
|
||||
// Allow all connections.
|
||||
// Comment this section out if you want to define specific restrictions.
|
||||
{
|
||||
"src": ["*"],
|
||||
"dst": ["*"],
|
||||
"ip": ["*"],
|
||||
},
|
||||
|
||||
// Allow users in "group:example" to access "tag:example", but only from
|
||||
// devices that are running macOS and have enabled Tailscale client auto-updating.
|
||||
// {"src": ["group:example"], "dst": ["tag:example"], "ip": ["*"], "srcPosture":["posture:autoUpdateMac"]},
|
||||
],
|
||||
|
||||
// Define postures that will be applied to all rules without any specific
|
||||
// srcPosture definition.
|
||||
// "defaultSrcPosture": [
|
||||
// "posture:anyMac",
|
||||
// ],
|
||||
|
||||
// Define device posture rules requiring devices to meet
|
||||
// certain criteria to access parts of your system.
|
||||
// "postures": {
|
||||
// // Require devices running macOS, a stable Tailscale
|
||||
// // version and auto update enabled for Tailscale.
|
||||
// "posture:autoUpdateMac": [
|
||||
// "node:os == 'macos'",
|
||||
// "node:tsReleaseTrack == 'stable'",
|
||||
// "node:tsAutoUpdate",
|
||||
// ],
|
||||
// // Require devices running macOS and a stable
|
||||
// // Tailscale version.
|
||||
// "posture:anyMac": [
|
||||
// "node:os == 'macos'",
|
||||
// "node:tsReleaseTrack == 'stable'",
|
||||
// ],
|
||||
// },
|
||||
|
||||
// Define users and devices that can use Tailscale SSH.
|
||||
"ssh": [
|
||||
// Allow all users to SSH into their own devices in check mode.
|
||||
// Comment this section out if you want to define specific restrictions.
|
||||
{
|
||||
"action": "check",
|
||||
"src": ["autogroup:member"],
|
||||
"dst": ["autogroup:self"],
|
||||
"users": ["autogroup:nonroot", "root"],
|
||||
},
|
||||
// Allow Erich to ssh on to the homelab server.
|
||||
{
|
||||
"src": ["blume.erich@gmail.com"],
|
||||
"dst": ["tag:homelab"],
|
||||
"users": ["autogroup:nonroot"],
|
||||
"action": "check",
|
||||
"checkPeriod": "12h0m0s",
|
||||
},
|
||||
],
|
||||
|
||||
"tagOwners": {
|
||||
// Grafana service host tag
|
||||
"tag:grafana": ["autogroup:admin"],
|
||||
|
||||
// This tag applies to instances which are meant to be accessible in my homelab. These instances can be SSH'ed in to by any member of the admin autogroup.
|
||||
"tag:homelab": ["autogroup:admin"],
|
||||
|
||||
// Kiwix, a local wiki server. I use it to create mirrors of wikipedia.
|
||||
"tag:kiwix": ["autogroup:admin"],
|
||||
|
||||
// Service tag for forgejo, scm host and code forge
|
||||
"tag:forge": ["autogroup:admin"],
|
||||
|
||||
// devpi pypi index
|
||||
"tag:devpi": ["autogroup:admin"],
|
||||
|
||||
// Loki log collection
|
||||
"tag:loki": ["autogroup:admin"],
|
||||
|
||||
// This tag is applied to resources modified by blumeops-pulumi IaC
|
||||
"tag:blumeops": ["autogroup:admin"],
|
||||
},
|
||||
|
||||
// Test access rules every time they're saved.
|
||||
// "tests": [
|
||||
// {
|
||||
// "src": "alice@example.com",
|
||||
// "accept": ["tag:example"],
|
||||
// "deny": ["100.101.102.103:443"],
|
||||
// },
|
||||
// ],
|
||||
}
|
||||
8
pulumi/pyproject.toml
Normal file
8
pulumi/pyproject.toml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[project]
|
||||
name = "blumeops-tailnet"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
"pulumi>=3.0.0",
|
||||
"pulumi-tailscale>=0.24.0",
|
||||
]
|
||||
Loading…
Add table
Add a link
Reference in a new issue