Initial commit

This commit is contained in:
sysadmin 2025-08-20 16:14:31 +01:00
commit 62f2690fb5
8 changed files with 594 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
*.swo
*.swp
.DS_Store

252
README.md Normal file
View File

@ -0,0 +1,252 @@
# myca-tools
Bash helpers for running a simple local root CA, issuing TLS certificates with SANs, and generating Nginx HTTPS reverse-proxy vhosts for internal services.
Do not use `.local`. Use `.lan` or a domain you control.
## Placeholders used in this README
- `NAME`
The service hostname you want to secure. Example: `app.lan`.
- `HOST_IP`
The local IP address of the server that presents the certificate. In a typical setup this is the machine running Nginx and hosting the service. Example: `192.168.1.50`.
- `BACKEND_HOST:PORT`
Where Nginx should proxy. Example: `127.0.0.1:8080`.
Replace these with your values before running commands.
## Features
- Initialise a root CA in `/etc/ssl/myca`
- Issue server certificates with DNS and IP SANs
- Sign existing CSRs
- Show and verify certificates
- Deploy certs to `/etc/nginx/ssl` and reload Nginx
- Generate a standard HTTPS reverse proxy vhost
## Requirements
- Debian or Ubuntu
- `openssl`
- `nginx` if you want reverse proxying
- Root access
## Install
```bash
git clone <your-repo-url> myca-tools
cd myca-tools
./install.sh
```
Installs:
/usr/local/sbin/myca.sh
/usr/local/sbin/mknginx-sni.sh
/usr/local/sbin/init-ca.sh
Prepares /etc/ssl/myca with certs, csrs, exts, and private.
## Quick start
1. Initialise a root CA if you do not already have one.
```bash
sudo init-ca.sh "/CN=My Local Root CA"
```
2. Trust the CA on this host.
```bash
sudo myca.sh trust-ca
```
3. Issue a cert for your internal service and deploy it to Nginx.
```bash
# Replace NAME and HOST_IP
sudo myca.sh issue NAME \
--dns NAME --ip HOST_IP --deploy-nginx
```
4. Create an Nginx vhost that uses the cert and proxies to your backend.
```bash
# Replace NAME and BACKEND_HOST:PORT
sudo mknginx-sni.sh NAME BACKEND_HOST:PORT
```
5. Ensure clients resolve the name.
```bash
HOST_IP NAME
```
6. Test.
```bash
myca.sh show NAME
myca.sh verify NAME
curl -Ik https://NAME
```
## Notes on SANs
You can omit --ip HOST_IP if clients always connect by DNS name and do not pin the IP.
If clients connect by IP, include that IP in SANs with --ip.
You can supply multiple --dns and --ip flags.
Scripts
myca.sh
Helper for issuing, signing, showing, verifying, and deploying certificates.
```bash
myca.sh COMMAND [args]
Commands:
issue NAME [--dns foo --ip 1.2.3.4 ...] [--days N] [--reuse-key] [--deploy-nginx]
sign NAME --csr /path/to/file.csr [--dns ... --ip ...] [--days N]
show NAME | /path/to/cert.crt
verify NAME | /path/to/cert.crt
list
trust-ca
Environment:
MYCA_DIR default /etc/ssl/myca
NGINX_SSL_DIR default /etc/nginx/ssl
```
Examples:
```bash
# Issue new key and cert with SANs
sudo myca.sh issue internal-api.lan --dns internal-api.lan --ip HOST_IP
# Reissue keeping the same key and deploy to Nginx
sudo myca.sh issue wiki.lan --dns wiki.lan --reuse-key --deploy-nginx
# Sign an existing CSR
sudo myca.sh sign portal.lan --csr /tmp/portal.csr --dns portal.lan --ip 10.0.0.5
# Show and verify
myca.sh show wiki.lan
myca.sh verify wiki.lan
# List issued certs
myca.sh list
```
Files are written to:
Keys /etc/ssl/myca/private/NAME.key
CSRs /etc/ssl/myca/csrs/NAME.csr
Certs /etc/ssl/myca/certs/NAME.crt
SAN /etc/ssl/myca/exts/NAME.ext
CA files expected:
/etc/ssl/myca/myCA.pem
/etc/ssl/myca/myCA.key
/etc/ssl/myca/myCA.srl created on first issuance
mknginx-sni.sh
Generate a standard HTTPS reverse proxy server block for a given name and backend.
```bash
sudo mknginx-sni.sh NAME [BACKEND_HOST:PORT]
# writes /etc/nginx/conf.d/NAME.conf
# proxies to BACKEND, default 127.0.0.1:8000
# uses /etc/nginx/ssl/NAME.crt and /etc/nginx/ssl/NAME.key
```
Example:
```bash
sudo mknginx-sni.sh internal-api.lan 127.0.0.1:9000
```
init-ca.sh
Initialise a new root CA in /etc/ssl/myca.
```bash
sudo init-ca.sh "/CN=My Local Root CA"
```
Creates:
Root key /etc/ssl/myca/myCA.key 4096 bit
Root cert /etc/ssl/myca/myCA.pem valid 10 years
Trusting your CA on clients
Linux Debian or Ubuntu:
```bash
sudo cp /etc/ssl/myca/myCA.pem /usr/local/share/ca-certificates/myCA.crt
sudo update-ca-certificates
```
ndows:
Import myCA.pem into Local Computer, Trusted Root Certification Authorities.
macOS:
Import myCA.pem into the System keychain in Keychain Access. Set Always Trust.
Browsers must trust the CA or you will see warnings.
Directory layout
```vbnet
/etc/ssl/myca/
├── myCA.key root key
├── myCA.pem root certificate
├── myCA.srl serial file
├── certs/ issued certificates
├── csrs/ certificate signing requests
├── exts/ SAN extension files
└── private/ server private keys
```
Nginx deploy target:
```swift
/etc/nginx/ssl/NAME.crt
/etc/nginx/ssl/NAME.key
```
Environment variables
Override defaults if needed:
```bash
export MYCA_DIR=/srv/pki
export NGINX_SSL_DIR=/etc/nginx/ssl
```
Security
Back up /etc/ssl/myca/myCA.key offline. Losing it prevents renewal under the same root.
Keep /etc/ssl/myca/private permissions intact. Keys are mode 600.
Keep this root CA for internal use only. Do not use it on the public internet.
Use shorter lifetimes for anything exposed beyond your LAN.
Troubleshooting
Missing CA: ensure myCA.pem and myCA.key exist under /etc/ssl/myca.
verify error:num=20: the client does not trust your CA. Install the CA to the client trust store.
Browser still warns after trusting CA: clear the sites cached cert or restart the browser.
Nginx reload fails: run nginx -t and fix the reported error.
Licence
MIT.

25
bin/init-ca.sh Normal file
View File

@ -0,0 +1,25 @@
#!/usr/bin/env bash
# Initialise a simple local root CA in /etc/ssl/myca
set -euo pipefail
MYCA_DIR="${MYCA_DIR:-/etc/ssl/myca}"
CN="${1:-/CN=Local Development Root CA}"
DAYS="${DAYS:-3650}"
install -d -m 755 "$MYCA_DIR" "$MYCA_DIR/certs" "$MYCA_DIR/csrs" "$MYCA_DIR/exts"
install -d -m 700 "$MYCA_DIR/private"
if [[ -e "$MYCA_DIR/myCA.key" || -e "$MYCA_DIR/myCA.pem" ]]; then
echo "CA already exists in $MYCA_DIR" >&2
exit 1
fi
openssl genrsa -out "$MYCA_DIR/myCA.key" 4096
chmod 600 "$MYCA_DIR/myCA.key"
openssl req -x509 -new -nodes -key "$MYCA_DIR/myCA.key" -sha256 -days "$DAYS" \
-out "$MYCA_DIR/myCA.pem" -subj "$CN"
chmod 644 "$MYCA_DIR/myCA.pem"
echo "Root CA created: $MYCA_DIR/myCA.pem"
echo "Private key: $MYCA_DIR/myCA.key"

44
bin/mknginx-sni.sh Normal file
View File

@ -0,0 +1,44 @@
#!/usr/bin/env bash
# Generate a standard SSL reverse proxy server block for NAME and backend HOST:PORT.
set -euo pipefail
name="${1:-}"; backend="${2:-127.0.0.1:8000}"
[[ -n "$name" ]] || { echo "Usage: mknginx-sni.sh NAME [BACKEND_HOST:PORT]"; exit 2; }
cat >/etc/nginx/conf.d/"$name".conf <<NGINX
upstream ${name//./_}_app { server $backend; keepalive 32; }
server {
listen 80;
server_name $name;
return 301 https://\$host\$request_uri;
}
server {
listen 443 ssl http2;
server_name $name;
ssl_certificate /etc/nginx/ssl/$name.crt;
ssl_certificate_key /etc/nginx/ssl/$name.key;
client_max_body_size 100m;
location / {
proxy_pass http://${name//./_}_app;
proxy_http_version 1.1;
proxy_set_header Host \$host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection \$connection_upgrade;
}
}
map \$http_upgrade \$connection_upgrade {
default upgrade;
'' close;
}
NGINX
nginx -t && systemctl reload nginx
echo "/etc/nginx/conf.d/$name.conf written"

221
bin/myca.sh Normal file
View File

@ -0,0 +1,221 @@
#!/usr/bin/env bash
# Simple local CA helper for issuing server certs with SANs, verifying, and optional nginx deploy.
set -euo pipefail
MYCA_DIR="${MYCA_DIR:-/etc/ssl/myca}"
CERT_DIR="$MYCA_DIR/certs"
KEY_DIR="$MYCA_DIR/private"
CSR_DIR="$MYCA_DIR/csrs"
EXT_DIR="$MYCA_DIR/exts"
NGINX_SSL_DIR="${NGINX_SSL_DIR:-/etc/nginx/ssl}"
usage() {
cat <<'USAGE'
myca.sh COMMAND [args]
Commands:
issue NAME [--dns foo --ip 1.2.3.4 ...] [--days N] [--reuse-key] [--deploy-nginx]
Create key + CSR + SAN ext, sign with CA, write cert.
sign NAME --csr /path/to/file.csr [--dns ... --ip ...] [--days N]
Sign an existing CSR with SANs.
show NAME|/path/to/cert.crt
Show subject, SANs, validity.
verify NAME|/path/to/cert.crt
Verify against CA.
list
List issued certs.
trust-ca
Install CA into Debian or Ubuntu trust store and update.
Environment:
MYCA_DIR Default /etc/ssl/myca
NGINX_SSL_DIR Default /etc/nginx/ssl
Examples:
myca.sh issue snipe-it.lan --dns snipe-it.lan --ip 192.168.17.51 --deploy-nginx
myca.sh show snipe-it.lan
myca.sh verify snipe-it.lan
Notes:
- CA files expected: $MYCA_DIR/myCA.pem and $MYCA_DIR/myCA.key
- Avoid .local hostnames. Use .lan or a domain you control.
USAGE
}
ensure_dirs() {
install -d -m 755 "$CERT_DIR" "$CSR_DIR" "$EXT_DIR"
install -d -m 700 "$KEY_DIR"
}
have_ca() {
[[ -s "$MYCA_DIR/myCA.pem" && -s "$MYCA_DIR/myCA.key" ]] || {
echo "Missing CA: $MYCA_DIR/myCA.pem or myCA.key" >&2; exit 1;
}
}
make_ext() {
local name="$1"; shift
local dns_arr=() ip_arr=()
while [[ $# -gt 0 ]]; do
case "$1" in
--dns) dns_arr+=("$2"); shift 2;;
--ip) ip_arr+=("$2"); shift 2;;
*) echo "Unknown ext arg: $1" >&2; exit 2;;
case_esac
esac
done
local has_cn=0
for d in "${dns_arr[@]:-}"; do [[ "$d" == "$name" ]] && has_cn=1; done
[[ $has_cn -eq 0 ]] && dns_arr=("$name" "${dns_arr[@]:-}")
{
echo "basicConstraints=CA:FALSE"
echo "keyUsage=digitalSignature,keyEncipherment"
echo "extendedKeyUsage=serverAuth"
echo "subjectAltName=@alt_names"
echo "[alt_names]"
local i=1
for d in "${dns_arr[@]:-}"; do echo "DNS.$i=$d"; i=$((i+1)); done
i=1
for ip in "${ip_arr[@]:-}"; do echo "IP.$i=$ip"; i=$((i+1)); done
} > "$EXT_DIR/$name.ext"
}
gen_key_csr() {
local name="$1"
openssl genrsa -out "$KEY_DIR/$name.key" 2048 >/dev/null 2>&1
chmod 600 "$KEY_DIR/$name.key"
openssl req -new -key "$KEY_DIR/$name.key" \
-out "$CSR_DIR/$name.csr" -subj "/CN=$name" >/dev/null 2>&1
}
sign_csr() {
local name="$1" days="$2"
local serial_flag
if [[ -s "$MYCA_DIR/myCA.srl" ]]; then
serial_flag=(-CAserial "$MYCA_DIR/myCA.srl")
else
serial_flag=(-CAcreateserial)
fi
openssl x509 -req -in "$CSR_DIR/$name.csr" \
-CA "$MYCA_DIR/myCA.pem" -CAkey "$MYCA_DIR/myCA.key" \
"${serial_flag[@]}" -out "$CERT_DIR/$name.crt" -days "$days" -sha256 \
-extfile "$EXT_DIR/$name.ext" >/dev/null 2>&1
chmod 644 "$CERT_DIR/$name.crt"
}
deploy_nginx() {
local name="$1"
install -d -m 755 "$NGINX_SSL_DIR"
install -m 644 "$CERT_DIR/$name.crt" "$NGINX_SSL_DIR/$name.crt"
install -m 600 "$KEY_DIR/$name.key" "$NGINX_SSL_DIR/$name.key"
if command -v nginx >/dev/null 2>&1; then
nginx -t && systemctl reload nginx || true
fi
echo "Deployed to $NGINX_SSL_DIR/$name.{crt,key}"
}
cmd_issue() {
local name days=825 reuse_key=0 deploy=0
local dns_args=() ip_args=()
shift
[[ $# -ge 1 ]] || { usage; exit 2; }
name="$1"; shift
while [[ $# -gt 0 ]]; do
case "$1" in
--days) days="$2"; shift 2;;
--reuse-key) reuse_key=1; shift;;
--deploy-nginx) deploy=1; shift;;
--dns) dns_args+=("$2"); shift 2;;
--ip) ip_args+=("$2"); shift 2;;
*) echo "Unknown option: $1" >&2; exit 2;;
esac
done
ensure_dirs; have_ca
make_ext "$name" $(for d in "${dns_args[@]:-}"; do printf -- " --dns %q" "$d"; done) \
$(for i in "${ip_args[@]:-}"; do printf -- " --ip %q" "$i"; done)
if [[ $reuse_key -eq 0 || ! -s "$KEY_DIR/$name.key" ]]; then
gen_key_csr "$name"
else
openssl req -new -key "$KEY_DIR/$name.key" \
-out "$CSR_DIR/$name.csr" -subj "/CN=$name" >/dev/null 2>&1
fi
sign_csr "$name" "$days"
echo "Issued: $CERT_DIR/$name.crt"
[[ $deploy -eq 1 ]] && deploy_nginx "$name"
}
cmd_sign_existing() {
local name csr_path="" days=825
local dns_args=() ip_args=()
shift
[[ $# -ge 1 ]] || { usage; exit 2; }
name="$1"; shift
while [[ $# -gt 0 ]]; do
case "$1" in
--csr) csr_path="$2"; shift 2;;
--days) days="$2"; shift 2;;
--dns) dns_args+=("$2"); shift 2;;
--ip) ip_args+=("$2"); shift 2;;
*) echo "Unknown option: $1" >&2; exit 2;;
esac
done
[[ -n "$csr_path" && -s "$csr_path" ]] || { echo "CSR not found" >&2; exit 1; }
ensure_dirs; have_ca
cp -f "$csr_path" "$CSR_DIR/$name.csr"
make_ext "$name" $(for d in "${dns_args[@]:-}"; do printf -- " --dns %q" "$d"; done) \
$(for i in "${ip_args[@]:-}"; do printf -- " --ip %q" "$i"; done)
sign_csr "$name" "$days"
echo "Signed: $CERT_DIR/$name.crt"
}
cmd_show() {
local target="${1:-}"
[[ -n "$target" ]] || { usage; exit 2; }
[[ "$target" == *.crt || "$target" == *.pem ]] || target="$CERT_DIR/$target.crt"
[[ -s "$target" ]] || { echo "Cert not found: $target" >&2; exit 1; }
openssl x509 -in "$target" -noout -subject -issuer -dates
echo "SANs:"
openssl x509 -in "$target" -noout -text | awk '/Subject Alternative Name/{f=1;next}/X509v3/{f=0}f'
}
cmd_verify() {
local target="${1:-}"
[[ -n "$target" ]] || { usage; exit 2; }
[[ "$target" == *.crt || "$target" == *.pem ]] || target="$CERT_DIR/$target.crt"
[[ -s "$target" ]] || { echo "Cert not found: $target" >&2; exit 1; }
have_ca
openssl verify -CAfile "$MYCA_DIR/myCA.pem" "$target"
}
cmd_list() {
ensure_dirs
ls -1 "$CERT_DIR"/*.crt 2>/dev/null || true
}
cmd_trust_ca() {
have_ca
install -m 644 "$MYCA_DIR/myCA.pem" /usr/local/share/ca-certificates/myCA.crt
update-ca-certificates
echo "CA installed to system trust."
}
main() {
[[ $# -ge 1 ]] || { usage; exit 2; }
case "$1" in
issue) cmd_issue "$@";;
sign) cmd_sign_existing "$@";;
show) shift; cmd_show "${1:-}";;
verify) shift; cmd_verify "${1:-}";;
list) cmd_list;;
trust-ca) cmd_trust_ca;;
-h|--help|help) usage;;
*) echo "Unknown command: $1" >&2; usage; exit 2;;
esac
}
main "$@"

View File

@ -0,0 +1,33 @@
upstream snipe_it_lan_app { server 127.0.0.1:8000; keepalive 32; }
server {
listen 80;
server_name snipe-it.lan;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name snipe-it.lan;
ssl_certificate /etc/nginx/ssl/snipe-it.lan.crt;
ssl_certificate_key /etc/nginx/ssl/snipe-it.lan.key;
client_max_body_size 100m;
location / {
proxy_pass http://snipe_it_lan_app;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}

10
install.sh Normal file
View File

@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -euo pipefail
prefix="/usr/local/sbin"
sudo install -d -m 700 /etc/ssl/myca/private
sudo install -d -m 755 /etc/ssl/myca/{certs,csrs,exts}
sudo install -m 755 bin/myca.sh "$prefix/myca.sh"
sudo install -m 755 bin/mknginx-sni.sh "$prefix/mknginx-sni.sh"
sudo install -m 755 bin/init-ca.sh "$prefix/init-ca.sh"
echo "Installed to $prefix. CA dir at /etc/ssl/myca"

5
uninstall.sh Normal file
View File

@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -euo pipefail
sudo rm -f /usr/local/sbin/{myca.sh,mknginx-sni.sh,init-ca.sh}
echo "Removed scripts. CA materials under /etc/ssl/myca left untouched."