Add Pulumi for tailnet IaC management #15
13 changed files with 231 additions and 0 deletions
|
|
@ -26,3 +26,5 @@
|
|||
tags: devpi_metrics
|
||||
- role: plex_metrics
|
||||
tags: plex_metrics
|
||||
- role: tailscale_serve
|
||||
tags: tailscale-serve
|
||||
|
|
|
|||
27
ansible/roles/tailscale_serve/defaults/main.yml
Normal file
27
ansible/roles/tailscale_serve/defaults/main.yml
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
# Tailscale serve configuration for this host
|
||||
# Each service maps a Tailscale service name to local endpoints
|
||||
|
||||
tailscale_services:
|
||||
- name: svc:grafana
|
||||
https:
|
||||
port: 443
|
||||
upstream: http://localhost:3000
|
||||
|
||||
- name: svc:forge
|
||||
https:
|
||||
port: 443
|
||||
upstream: http://localhost:3001
|
||||
tcp:
|
||||
port: 22
|
||||
upstream: tcp://localhost:2200
|
||||
|
||||
- name: svc:kiwix
|
||||
https:
|
||||
port: 443
|
||||
upstream: http://localhost:5501
|
||||
|
||||
- name: svc:pypi
|
||||
https:
|
||||
port: 443
|
||||
upstream: http://127.0.0.1:3141
|
||||
6
ansible/roles/tailscale_serve/meta/main.yml
Normal file
6
ansible/roles/tailscale_serve/meta/main.yml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
dependencies:
|
||||
- role: grafana
|
||||
- role: forgejo
|
||||
- role: kiwix
|
||||
- role: devpi
|
||||
25
ansible/roles/tailscale_serve/tasks/main.yml
Normal file
25
ansible/roles/tailscale_serve/tasks/main.yml
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
- name: Get current tailscale serve status
|
||||
ansible.builtin.command: tailscale serve status --json
|
||||
register: serve_status
|
||||
changed_when: false
|
||||
|
||||
- name: Configure HTTPS services
|
||||
ansible.builtin.command: >
|
||||
tailscale serve --service="{{ item.name }}"
|
||||
--https={{ item.https.port }} {{ item.https.upstream }}
|
||||
loop: "{{ tailscale_services }}"
|
||||
when: item.https is defined
|
||||
register: https_result
|
||||
changed_when: "'already serving' not in https_result.stderr | default('')"
|
||||
failed_when: false
|
||||
|
||||
- name: Configure TCP services
|
||||
ansible.builtin.command: >
|
||||
tailscale serve --service="{{ item.name }}"
|
||||
--tcp={{ item.tcp.port }} {{ item.tcp.upstream }}
|
||||
loop: "{{ tailscale_services }}"
|
||||
when: item.tcp is defined
|
||||
register: tcp_result
|
||||
changed_when: "'already serving' not in tcp_result.stderr | default('')"
|
||||
failed_when: false
|
||||
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