Skip to content
Merged
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
57 changes: 57 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Lint

on:
push:
branches: ["main"]
pull_request:
branches: ["main"]

jobs:
shellcheck:
name: ShellCheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Run ShellCheck
uses: ludeeus/action-shellcheck@master
with:
scandir: "./scripts"
severity: warning

validate-env-example:
name: Validate env.example completeness
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Check all template variables are documented in env.example
run: |
# Extract variables used in templates (${VAR} form)
tmpl_vars=$(grep -hoP '\$\{\K[A-Z_]+(?=\})' config/*.tmpl | sort -u)

# Extract variable names defined in env.example
doc_vars=$(grep -oP '^[A-Z_]+(?==)' config/env.example | sort -u)

missing=""
for var in $tmpl_vars; do
if ! echo "$doc_vars" | grep -qx "$var"; then
missing="$missing $var"
fi
done

if [[ -n "$missing" ]]; then
echo "ERROR: The following template variables are not documented in config/env.example:"
echo "$missing" | tr ' ' '\n' | grep -v '^$'
exit 1
fi
echo "All template variables are documented."

validate-sudoers:
name: Validate sudoers syntax
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Check sudoers drop-in with visudo
run: visudo -cf config/sudoers.d/domain-groups
47 changes: 0 additions & 47 deletions LinuxConfig

This file was deleted.

45 changes: 44 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,45 @@
# LinuxConfig
Basic *nix config script. Sets hostname, FQDN, IP address, updates packages, creates a non root sudoer, limits SSH to IPv4, restarts sshd, creates a .ssh directory and sets permissions, and finally sets permissions on public key directory and file.

A modular server build pipeline for bootstrapping RHEL-based Linux hosts into an Active Directory environment with centralized subscription management.

## Pipeline stages

| Script | Description |
|--------|-------------|
| `scripts/base-setup.sh` | Hostname, package updates, SSH hardening, admin user creation |
| `scripts/ad-join.sh` | AD domain join, Kerberos, SSSD, and sudo configuration |
| `scripts/satellite-register.sh` | Red Hat Satellite registration and remote management tooling |
| `scripts/ad-rejoin-cron.sh` | Daily cron job to recover domain membership if lost |

Run all stages in sequence via:

```bash
sudo -E ./scripts/deploy.sh
```

Individual scripts can be run standalone for partial deployments or re-runs of a specific stage.

## Configuration

Copy `config/env.example` and fill in values for your environment:

```bash
cp config/env.example config/env
# edit config/env
set -a && source config/env && set +a
sudo -E ./scripts/deploy.sh
```

For CI/CD, configure values as GitHub Actions secrets and variables (see `config/env.example` for the full list and which should be treated as secrets).

## Config templates

`config/krb5.conf.tmpl` and `config/sssd.conf.tmpl` are applied via `envsubst` during the AD join stage — edit these to adjust Kerberos or SSSD behaviour without touching the scripts themselves.

## Linting

The included GitHub Actions workflow (`.github/workflows/lint.yml`) runs on every push and pull request:

- **ShellCheck** — static analysis on all scripts
- **sudoers validation** — `visudo -c` on the sudoers drop-in
- **Template coverage** — verifies all template variables are documented in `env.example`
34 changes: 34 additions & 0 deletions config/env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Environment variable reference for LinuxConfig deployment scripts.
#
# Copy this file, fill in real values, and source it before running scripts
# locally — or configure these as GitHub Actions secrets/variables (see below).
#
# GitHub Actions secrets (sensitive — set under Settings > Secrets):
# AD_JOIN_PASS
# SATELLITE_ACTIVATION_KEY
#
# GitHub Actions variables (non-sensitive — set under Settings > Variables):
# All others below

# ── Base setup (base-setup.sh) ───────────────────────────────────────────────
HOSTNAME=myserver
FQDN=myserver.example.com
ADMIN_USER=sysadmin
ADMIN_SSH_KEY= # Optional: paste public key value here

# ── Active Directory (ad-join.sh, ad-rejoin-cron.sh) ────────────────────────
AD_DOMAIN=example.com
AD_SERVER=dc-01.example.com
KRB5_REALM=EXAMPLE.COM # Must be uppercase
AD_JOIN_USER=linux_join # Service account with domain join rights
AD_JOIN_PASS=CHANGEME # SECRET — do not commit real value

# ── Satellite (satellite-register.sh) ───────────────────────────────────────
SATELLITE_SERVER=satellite.example.com
SATELLITE_ORG=MyOrg
SATELLITE_ACTIVATION_KEY=CHANGEME # SECRET — do not commit real value
SATELLITE_ENV=Library # Optional: Satellite content view environment

# ── AD rejoin cron (ad-rejoin-cron.sh) ──────────────────────────────────────
WHEEL_USER= # Optional: local user to add to wheel group
REJOIN_SCRIPT=/usr/local/sbin/ad-rejoin.sh # Default install path
34 changes: 34 additions & 0 deletions config/krb5.conf.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Kerberos 5 configuration
# Variables substituted at deploy time via envsubst:
# AD_DOMAIN, AD_SERVER, KRB5_REALM

# To opt out of the system crypto-policies configuration of krb5, remove the
# symlink at /etc/krb5.conf.d/crypto-policies which will not be recreated.
includedir /etc/krb5.conf.d/

[logging]
default = FILE:/var/log/krb5libs.log
kdc = FILE:/var/log/krb5kdc.log
admin_server = FILE:/var/log/kadmind.log

[libdefaults]
dns_lookup_realm = true
ticket_lifetime = 24h
renew_lifetime = 7d
forwardable = true
rdns = false
pkinit_anchors = FILE:/etc/pki/tls/certs/ca-bundle.crt
spake_preauth_groups = edwards25519
dns_canonicalize_hostname = fallback
qualify_shortname = ""
default_ccache_name = KEYRING:persistent:%{uid}

[realms]
${KRB5_REALM} = {
kdc = ${AD_SERVER}
admin_server = ${AD_SERVER}
}

[domain_realm]
.${AD_DOMAIN} = ${KRB5_REALM}
${AD_DOMAIN} = ${KRB5_REALM}
23 changes: 23 additions & 0 deletions config/sssd.conf.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# SSSD configuration
# Variables substituted at deploy time via envsubst:
# AD_DOMAIN, AD_SERVER, KRB5_REALM

[sssd]
domains = ${AD_DOMAIN}
config_file_version = 2
services = nss, pam

[domain/${AD_DOMAIN}]
ad_server = ${AD_SERVER}
ad_domain = ${AD_DOMAIN}
krb5_realm = ${KRB5_REALM}
realmd_tags = manages-system joined-with-adcli
id_provider = ad
access_provider = ad

cache_credentials = True
krb5_store_password_if_offline = True
ldap_id_mapping = True
use_fully_qualified_names = False
fallback_homedir = /home/%u
default_shell = /bin/bash
22 changes: 22 additions & 0 deletions config/sudoers.d/domain-groups
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
## Command aliases for domain group sudo rules

Cmnd_Alias RESTRICTED = /bin/vi /etc/sudoers, \
/bin/su - root, \
/bin/su - , \
/usr/sbin/visudo

Cmnd_Alias SHELLS = /bin/sh, \
/bin/ksh, \
/bin/bash, \
/bin/zsh, \
/bin/csh, \
/bin/tcsh, \
/usr/bin/login, \
/usr/bin/su

## Domain group permissions
# LinuxAdmins: full sudo except shell escalation and sudoers editing
"%LinuxAdmins" ALL=(ALL) ALL, !SHELLS, !RESTRICTED

# Domain Admins: unrestricted sudo
"%domain admins" ALL=(ALL) NOPASSWD: ALL
64 changes: 64 additions & 0 deletions scripts/ad-join.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#!/bin/bash
set -euo pipefail
trap 'echo "ERROR: script failed at line $LINENO" >&2' ERR

# Joins the host to Active Directory and configures Kerberos, SSSD, and sudo.
#
# Required variables:
# AD_DOMAIN AD domain name (e.g. example.com)
# AD_SERVER AD domain controller FQDN (e.g. dc-01.example.com)
# KRB5_REALM Kerberos realm (uppercase) (e.g. EXAMPLE.COM)
# AD_JOIN_USER AD account used to join (e.g. linux_join)
# AD_JOIN_PASS Password for AD_JOIN_USER

: "${AD_DOMAIN:?AD_DOMAIN must be set}"
: "${AD_SERVER:?AD_SERVER must be set}"
: "${KRB5_REALM:?KRB5_REALM must be set}"
: "${AD_JOIN_USER:?AD_JOIN_USER must be set}"
: "${AD_JOIN_PASS:?AD_JOIN_PASS must be set}"

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# Update packages
yum update -y

# Crypto policy required for AD Kerberos compatibility
update-crypto-policies --set DEFAULT:AD-SUPPORT

# Verify LDAP SRV records exist before attempting join
dig -t SRV "_ldap._tcp.${AD_DOMAIN}" +short | grep -q '.' \
|| { echo "ERROR: No LDAP SRV records found for ${AD_DOMAIN}" >&2; exit 1; }

# Install required packages
yum install -y realmd adcli sssd sssd-tools oddjob oddjob-mkhomedir \
krb5-workstation krb5-libs authselect

# Apply Kerberos config from template
envsubst < "${SCRIPT_DIR}/../config/krb5.conf.tmpl" > /etc/krb5.conf

# Join domain (idempotent)
if ! realm list | grep -q "domain-name: ${AD_DOMAIN}"; then
echo "Joining domain ${AD_DOMAIN}..."
echo "$AD_JOIN_PASS" | realm join --user="$AD_JOIN_USER" "$AD_SERVER"
else
echo "Already joined to ${AD_DOMAIN}, skipping join."
fi

# Apply SSSD config from template
envsubst < "${SCRIPT_DIR}/../config/sssd.conf.tmpl" > /etc/sssd/sssd.conf
chown root:root /etc/sssd/sssd.conf
chmod 600 /etc/sssd/sssd.conf

# Select SSSD auth profile with home directory creation
authselect select sssd with-mkhomedir --force

# Enable and start SSSD
systemctl enable sssd.service
systemctl restart sssd.service

# Install sudoers drop-in (visudo validates before install)
visudo -cf "${SCRIPT_DIR}/../config/sudoers.d/domain-groups"
install -m 0440 "${SCRIPT_DIR}/../config/sudoers.d/domain-groups" \
/etc/sudoers.d/domain-groups

echo "AD join complete for domain ${AD_DOMAIN}"
Loading
Loading