mirror of
https://github.com/immich-app/yucca.git
synced 2026-09-30 13:33:00 +08:00
feat(ceph): authorize ops SSH keys from the identity registry (#174)
This commit is contained in:
@@ -16,6 +16,7 @@ inventories/*/inventory.ini
|
||||
inventories/*/inventory-provision.ini
|
||||
inventories/*/inventory-destroy.ini
|
||||
inventories/*/secrets.yml.tpl
|
||||
inventories/*/group_vars/all/operators.yml
|
||||
|
||||
# host_vars IS committed (per-node hardware facts — stable, part of the inventory
|
||||
# source of truth). Operator-local overrides use host_vars/*.local.yml if needed.
|
||||
|
||||
@@ -7,6 +7,12 @@ baseline_ops_user: ops
|
||||
baseline_ops_uid: 1001
|
||||
baseline_ops_sudo: "ALL" # "ALL" = password required; "NOPASSWD:ALL" for dev
|
||||
|
||||
# Operator SSH public keys authorized on the ops account. Sourced from the
|
||||
# identity registry (tf/shared/modules/identity, members of a `server`-mapped
|
||||
# group) and rendered into group_vars/all/operators.yml by
|
||||
# render-inventories.sh. Default empty: with no keys, ops stays password-only.
|
||||
ops_authorized_keys: []
|
||||
|
||||
# --- /etc/hosts ---
|
||||
# Rendered from the same cluster variables used by ceph_deploy/hosts.j2.
|
||||
# Keeps host entries converged even if ceph_deploy hasn't run yet.
|
||||
|
||||
@@ -36,3 +36,14 @@
|
||||
dest: "/etc/sudoers.d/{{ baseline_ops_user }}"
|
||||
mode: '0440'
|
||||
validate: "visudo -cf %s"
|
||||
|
||||
# Operator SSH access to the shared ops account, sourced from the identity
|
||||
# registry (rendered into group_vars/all/operators.yml). exclusive: true makes
|
||||
# the registry the single source of truth -- keys not in it are removed. Skipped
|
||||
# when the list is empty so ops stays password-only rather than getting wiped.
|
||||
- name: Authorize operator SSH keys on the ops account
|
||||
ansible.posix.authorized_key:
|
||||
user: "{{ baseline_ops_user }}"
|
||||
key: "{{ ops_authorized_keys | join('\n') }}"
|
||||
exclusive: true
|
||||
when: ops_authorized_keys | length > 0
|
||||
|
||||
@@ -47,6 +47,7 @@ for cluster, spec in data.items():
|
||||
os.makedirs(d, exist_ok=True)
|
||||
for fname, content in spec["files"].items():
|
||||
p = os.path.join(d, fname)
|
||||
os.makedirs(os.path.dirname(p), exist_ok=True) # fname may include subdirs (e.g. group_vars/all/)
|
||||
with open(p, "w") as fh:
|
||||
fh.write(content)
|
||||
os.chmod(p, 0o644)
|
||||
|
||||
@@ -39,12 +39,25 @@ output "cluster_summaries" {
|
||||
# Rendered content lives in a TF OUTPUT (not in local_file resources), so the
|
||||
# shared remote state never records a checkout-specific filesystem path. See
|
||||
# the module's rendering.tf for the full rationale.
|
||||
# Canonical user/group registry. Used here only for server operator access:
|
||||
# members of a `server`-mapped group get their SSH keys into the node ops
|
||||
# account's authorized_keys (rendered as group_vars/all/operators.yml below).
|
||||
module "identity" {
|
||||
source = "../../../shared/modules/identity"
|
||||
}
|
||||
|
||||
output "render" {
|
||||
description = "Per-cluster { dirname, files } for the local render wrapper."
|
||||
value = {
|
||||
for k, m in module.cluster : k => {
|
||||
dirname = m.inventory_dirname
|
||||
files = m.rendered_files
|
||||
files = merge(m.rendered_files, {
|
||||
# Operator SSH keys for the shared ops account, from the identity
|
||||
# registry. Public keys only; gitignored (rendered, not committed).
|
||||
"group_vars/all/operators.yml" = yamlencode({
|
||||
ops_authorized_keys = module.identity.server_authorized_keys
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,14 @@ output "groups" {
|
||||
value = var.groups
|
||||
}
|
||||
|
||||
output "server_authorized_keys" {
|
||||
description = "Union of SSH public keys (ed25519 + rsa) for members of any group with a `server` mapping. For populating a shared node ops account's authorized_keys."
|
||||
value = sort(distinct(flatten([
|
||||
for uname, u in var.users : concat(u.ssh_ed25519_keys, u.ssh_rsa_keys)
|
||||
if length([for g in u.groups : g if try(var.groups[g].server, null) != null]) > 0
|
||||
])))
|
||||
}
|
||||
|
||||
output "members_of" {
|
||||
description = "Group name -> sorted member usernames. Lets a consumer (e.g. servers) provision a group's people."
|
||||
value = {
|
||||
|
||||
@@ -23,7 +23,7 @@ variable "users" {
|
||||
nutgood = {
|
||||
full_name = "Antoine"
|
||||
uid = 3000
|
||||
groups = ["fabric_admins"]
|
||||
groups = ["fabric_admins", "server_admins"]
|
||||
ssh_ed25519_keys = [
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOaH71qha6kLO2qRu+w6C5wPpWhkiBjEUeY1fAjAVApR"
|
||||
]
|
||||
@@ -31,7 +31,7 @@ variable "users" {
|
||||
andy = {
|
||||
full_name = "Andy"
|
||||
uid = 3001
|
||||
groups = ["fabric_viewer"]
|
||||
groups = ["fabric_viewer", "server_admins"]
|
||||
ssh_ed25519_keys = [
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO7XdW03gJKyABt5KCMxOLPb5sOGTXuZV0OHc1Ro46Nt andy@futo.org"
|
||||
]
|
||||
@@ -40,7 +40,7 @@ variable "users" {
|
||||
}
|
||||
|
||||
variable "groups" {
|
||||
description = "Groups keyed by name. A group carries per-system role mappings; `fabric` grants switch-fabric rights to its members. (A `server` mapping will be added when server provisioning is wired up.)"
|
||||
description = "Groups keyed by name. A group carries per-system role mappings; `fabric` grants switch-fabric rights, `server` grants login access to provisioned servers (their members' SSH keys flow to the node ops account)."
|
||||
type = map(object({
|
||||
description = optional(string)
|
||||
# How membership manifests on the switch fabric. Set exactly one of:
|
||||
@@ -50,6 +50,12 @@ variable "groups" {
|
||||
class = optional(string)
|
||||
permissions = optional(list(string))
|
||||
}))
|
||||
# How membership manifests on servers. Presence grants login access; today
|
||||
# members' SSH keys populate the shared node ops account's authorized_keys.
|
||||
# `sudo` is reserved for per-user accounts if/when those are wired up.
|
||||
server = optional(object({
|
||||
sudo = optional(string)
|
||||
}))
|
||||
}))
|
||||
|
||||
default = {
|
||||
@@ -61,6 +67,10 @@ variable "groups" {
|
||||
description = "Read-only access to the switch fabric."
|
||||
fabric = { class = "read-only" }
|
||||
}
|
||||
server_admins = {
|
||||
description = "Login access to provisioned servers (e.g. the ceph nodes)."
|
||||
server = { sudo = "ALL" }
|
||||
}
|
||||
}
|
||||
|
||||
validation {
|
||||
|
||||
Reference in New Issue
Block a user