1
0
mirror of https://gitlab.alpinelinux.org/alpine/cloud/tiny-cloud.git synced 2026-06-21 00:07:16 +03:00

Compare commits

...

9 Commits

Author SHA1 Message Date
5e4dac2d46 Merge branch 'feature/add_passwd_hash' into 'main'
Add passwd_hash

See merge request alpine/cloud/tiny-cloud!136
2026-05-18 14:55:56 +00:00
Leonardo Arena
9cf6da5147 add LVM support for root partition 2026-05-18 12:56:13 +02:00
Leonardo Arena
b732566c65 Add support for overriding default provider's API version 2026-05-15 11:54:07 +02:00
Leonardo Arena
ffff8fc35d Allow to override default IMDS endpoint 2026-05-11 09:53:14 +02:00
Aleksandr Berkuta
cc2b219f04 Add tests for passwd and hashed_passwd user-data parameters 2026-05-09 21:28:31 +00:00
Aleksandr Berkuta
a365c69683 Add passwd parameter to user-data 2026-05-09 21:28:31 +00:00
Aleksandr Berkuta
29f40b34c2 Undo auto-formatted space deletions 2026-05-09 21:28:31 +00:00
Aleksandr Berkuta
168bb06699 fix: separate lock_passwd and hashed_passwd 2026-05-09 21:28:31 +00:00
Aleksandr Berkuta
cfe3c2dda5 Add passwd_hash
Without password hash '$user:*' to `chpasswd -e` will result to
inability for user to login, or change password. So I've add parameter
passwd_hash for the user. Password hash could be generated via command
`openssl passwd -5 your_password`.
2026-05-09 21:28:31 +00:00
18 changed files with 284 additions and 23 deletions

View File

@ -49,6 +49,7 @@ IMDS data.
As Tiny Cloud is meant to be tiny, it has few dependencies:
* Busybox (`ash`, `wget`, etc.)
* `e2fsprogs-extra` (for `resize2fs`)
* `lvm2` (for resizing LVM volumes)
* `openssh-server`
* `partx`
* `sfdisk`
@ -116,6 +117,30 @@ Alternatively, you can add `tinycloud=cloud=<cloud>` (preferred) or `ds=<cloud>`
`/sys/class/dmi/id/product_serial` (QEMU hack) is another way to explicitly
choose a cloud provider.
### Custom Metadata Service Endpoint
For custom or non-standard metadata services, you can
override the default IMDS endpoint by setting `IMDS_ENDPOINT` in
`/etc/tiny-cloud.conf`:
```sh
IMDS_ENDPOINT=192.0.2.1:50061
```
The default endpoint is `169.254.169.254` for most cloud providers. This
setting allows you to specify a custom IP address and optional port for the
metadata service.
### Metadata API Version
Each provider's API has a built-in default version. You can override the
version specifying a value for IMDS_API_VERSION in `/etc/tiny-cloud.conf`:
```sh
# e.g. use AWS IMDSv1 (2009-04-04)
IMDS_API_VERSION=2009-04-04
```
## Operation
The first time an instance boots -- either freshly instantiated from an image,

View File

@ -20,7 +20,7 @@
* Support for `#network-config`?
* Support LVM partitioning and non-`ext[234]` filesystems?
* Support non-`ext[234]` filesystems?
* Support other cloud providers...
* IBM

View File

@ -41,7 +41,6 @@ fi
unset \
IMDS_HEADER \
IMDS_URI \
IMDS_QUERY
unset -f \
_imds_token \
@ -52,7 +51,8 @@ unset -f \
### default variables/functions
# Common to many clouds
IMDS_ENDPOINT="169.254.169.254"
# Can be overridden in /etc/tiny-cloud.conf
: "${IMDS_ENDPOINT:=169.254.169.254}"
# Common to AWS and NoCloud(ish)
IMDS_HOSTNAME="meta-data/hostname"

View File

@ -33,6 +33,14 @@ Blank lines and shell comments are ignored.
Default user account for instance SSH keys and default-user setup. The default
is *alpine*.
*IMDS_API_VERSION*=<version>
Provider's API version to use. Providers that have versioned APIs have
built-in default values.
*IMDS_ENDPOINT*=<ip_address>
Provider endpoint IP address to use. Defaults to 169.254.169.254 for many
providers.
*IMDS_TOKEN_TTL*=<seconds>
Metadata token lifetime in seconds for AWS metadata access. This is only used
by the AWS provider. The default is *5*.
@ -43,6 +51,10 @@ Blank lines and shell comments are ignored.
*TINY_CLOUD_LOGS*=<path>
Log directory used by tiny-cloud. The default is _/var/log_.
*TINY_CLOUD_LV_ALLOCATE*=<percentage>
How much space to allocate when expanding the "/" LVM logical volume in
percentage. The default is 85 percent of the volume group's space.
*SKIP_INIT_ACTIONS*=<action> ...
Whitespace-separated list of init actions to skip during phase execution.

View File

@ -5,14 +5,23 @@
IMDS_HEADER="X-aws-ec2-metadata-token"
IMDS_TOKEN_TTL_HEADER="X-aws-ec2-metadata-token-ttl-seconds"
: "${IMDS_TOKEN_TTL:=5}"
IMDS_URI="latest"
: "${IMDS_API_VERSION:=latest}"
IMDS_URI="$IMDS_API_VERSION"
_imds_token() {
# Only try to get token if using IMDSv2
# IMDSv1: API versions 2009-04-04 and earlier (no token support)
# IMDSv2: API versions 2009-04-05 and later, or 'latest' (requires token)
expr "$IMDS_API_VERSION" "<=" "2009-04-04" > /dev/null && return
# IMDSv2 - request token
printf "PUT /latest/api/token HTTP/1.0\r\n%s: %s\r\n\r\n" \
"$IMDS_TOKEN_TTL_HEADER" "$IMDS_TOKEN_TTL" \
| nc -w 1 "$IMDS_ENDPOINT" 80 | tail -n 1
}
_imds_header() {
echo "$IMDS_HEADER: $(_imds_token)"
local token="$(_imds_token)"
if [ -n "$token" ]; then
echo "$IMDS_HEADER: $token"
fi
}

View File

@ -2,8 +2,9 @@
# vim:set filetype=sh:
# shellcheck shell=sh
: "${IMDS_API_VERSION:=2021-05-01}"
IMDS_HEADER="Metadata"
IMDS_QUERY="?format=text&api-version=2021-05-01"
IMDS_QUERY="?format=text&api-version=$IMDS_API_VERSION"
IMDS_URI="metadata/instance"
IMDS_HOSTNAME="compute/name"

View File

@ -2,7 +2,8 @@
# vim: set filetype=sh:
# shellcheck shell=sh
IMDS_URI="metadata/v1"
: "${IMDS_API_VERSION:=v1}"
IMDS_URI="metadata/$IMDS_API_VERSION"
IMDS_HOSTNAME="hostname"
IMDS_LOCAL_HOSTNAME="$IMDS_HOSTNAME"
IMDS_SSH_KEYS="public-keys"

View File

@ -2,8 +2,9 @@
# vim:set filetype=sh:
# shellcheck shell=sh
: "${IMDS_API_VERSION:=v1}"
IMDS_HEADER="Metadata-Flavor"
IMDS_URI="computeMetadata/v1"
IMDS_URI="computeMetadata/$IMDS_API_VERSION"
IMDS_HOSTNAME="instance/hostname"
IMDS_LOCAL_HOSTNAME="$IMDS_HOSTNAME"

View File

@ -2,7 +2,8 @@
# vim:set filetype=sh:
# shellcheck shell=sh
IMDS_BASE_URI="hetzner/v1"
: "${IMDS_API_VERSION:=v1}"
IMDS_BASE_URI="hetzner/$IMDS_API_VERSION"
IMDS_URI="$IMDS_BASE_URI/metadata"
IMDS_HOSTNAME="hostname"

View File

@ -4,7 +4,8 @@
# https://linuxcontainers.org/incus/docs/main/dev-incus/
IMDS_URI=/1.0/meta-data
: "${IMDS_API_VERSION:=1.0}"
IMDS_URI="/$IMDS_API_VERSION/meta-data"
IMDS_LOCAL_HOSTNAME="local-hostname"
IMDS_HOSTNAME="local-hostname"
IMDS_ENDPOINT=local:/dev/incus/sock

View File

@ -2,8 +2,9 @@
# vim:set filetype=sh:
# shellcheck shell=sh
: "${IMDS_API_VERSION:=v2}"
IMDS_HEADER="Authorization"
IMDS_URI="opc/v2"
IMDS_URI="opc/$IMDS_API_VERSION"
IMDS_HOSTNAME="instance/hostname"
IMDS_LOCAL_HOSTNAME="$IMDS_HOSTNAME"

View File

@ -45,14 +45,43 @@ init__expand_root() {
*) return;;
esac
if [ -n "$partition" ]; then
# it's a partition, resize it
local volume=$(readlink -f "$SYS/class/block/${dev#/dev/}/..")
volume="/dev/${volume##*/}"
echo ", +" | $MOCK sfdisk -q --no-reread -N "$partition" "$volume"
$MOCK partx -u "$volume"
# check if this is an LVM logical volume
if lvs --noheadings -o lv_name "$dev" >/dev/null 2>&1; then
# LVM volume detected
# find the physical volume backing this LV
local vg_name=$(lvs --noheadings -o vg_name "$dev" 2>/dev/null | tr -d ' ')
local pv_dev=$(pvs --noheadings -o pv_name -S "vg_name=$vg_name" 2>/dev/null | tr -d ' ' | head -n1)
# get the partition number if PV is on a partition
local pv_partition=$(cat "$SYS/class/block/${pv_dev#/dev/}/partition" 2>/dev/null)
# only resize partition if PV is on a partition (not whole disk)
if [ -n "$pv_partition" ]; then
# resize the partition containing the PV
local pv_volume=$(readlink -f "$SYS/class/block/${pv_dev#/dev/}/..")
pv_volume="/dev/${pv_volume##*/}"
echo ", +" | $MOCK sfdisk -q --no-reread -N "$pv_partition" "$pv_volume"
$MOCK partx -u "$pv_volume"
fi
# resize the physical volume (works for both partition and whole disk)
$MOCK pvresize "$pv_dev"
# extend the logical volume
# default leave 15% for snapshots
$MOCK lvextend -l +${TINY_CLOUD_LV_ALLOCATE:-85}%VG "$dev"
else
# standard partition handling (non-LVM)
if [ -n "$partition" ]; then
# it's a partition, resize it
local volume=$(readlink -f "$SYS/class/block/${dev#/dev/}/..")
volume="/dev/${volume##*/}"
echo ", +" | $MOCK sfdisk -q --no-reread -N "$partition" "$volume"
$MOCK partx -u "$volume"
fi
fi
# resize filesystem
# resize filesystem (common for both LVM and non-LVM)
if [ -e "$dev" ] || [ -n "$MOCK" ]; then
$MOCK resize2fs "$dev"
fi

View File

@ -8,7 +8,16 @@
# User account where instance SSH keys are installed
#CLOUD_USER=alpine
# IMDS token validity, in seconds (AWS only)
# IMDS endpoint override (IP:PORT or IP)
# Defaults to 169.254.169.254 for most clouds
# Useful for custom metadata services
#IMDS_ENDPOINT=169.254.169.254
# IMDS API version
# Most providers have a default version, overrideable here if necessary
#IMDS_API_VERSION=""
# IMDS token validity, in seconds (AWS only, IMDSv2)
#IMDS_TOKEN_TTL=5
# Location of var directory
@ -17,6 +26,10 @@
# Location of log directory
#TINY_CLOUD_LOGS=/var/log
# How much space to allocate when expanding the "/" LVM logical volume in percentage
# Default: leave 15% unallocated for snapshots
#TINY_CLOUD_LV_ALLOCATE=85
# Explicitly skip these (whitespace delimited) things during init
# examples: expand_root set_hostname set_ssh_keys save_userdata
# decompress_userdata run_userdata

View File

@ -244,7 +244,7 @@ in_list() {
init__userdata_users() {
local i users="$(get_userdata users)"
for i in $users; do
local name="" gecos="" homedir="" shell="" primary_group="" groups=""
local name="" gecos="" homedir="" shell="" primary_group="" groups="" passwd="" hashed_passwd=""
local system=false no_create_home=false lock_passwd=true
local keys="$(get_userdata users/$i)"
if [ "$i" = 1 ] && [ "$keys" = "default" ]; then
@ -273,6 +273,10 @@ init__userdata_users() {
if in_list no_create_home $keys; then
no_create_home="$(get_userdata users/$i/no_create_home)"
fi
if in_list passwd $keys; then
passwd="$(get_userdata users/$i/passwd)"
echo "${user}:${passwd}" | $MOCK chpasswd -e
fi
if getent passwd "$user" >/dev/null; then
log -i -t "$phase" info "$ACTION: user $user already exists"
@ -286,12 +290,17 @@ init__userdata_users() {
$MOCK adduser -D ${gecos:+-g "$gecos"} ${homedir:+-h "$homedir"} ${shell:+-s "$shell"} ${primary_group:+-G "$primary_group"} ${system:+-S} ${no_create_home:+-H} "$name"
fi
if in_list hashed_passwd $keys; then
hashed_passwd="$(get_userdata users/$i/hashed_passwd)"
echo "${name}:${hashed_passwd}" | $MOCK chpasswd -e
fi
if in_list lock_passwd $keys; then
lock_passwd="$(get_userdata users/$i/lock_passwd)"
fi
if [ "$lock_passwd" != "false" ]; then
echo "$name:*" | $MOCK chpasswd -e
if [ "$lock_passwd" != "false" ] && [ -z "$hashed_passwd" ]; then
echo "${name}:*" | $MOCK chpasswd -e
fi
if in_list ssh_authorized_keys $keys; then

View File

@ -41,6 +41,10 @@ init_tests \
imds_ssh_keys_oci \
imds_ssh_keys_scaleway \
\
imds_aws_api_version_imdsv1 \
imds_aws_api_version_imdsv2_explicit \
imds_aws_api_version_imdsv2_latest \
\
imds_nocloud_cmdline_local_hostname \
imds_nocloud_smbios_local_hostname \
\
@ -198,6 +202,55 @@ EOF
CLOUD="scaleway" atf_check -o match:"$key" imds @ssh-keys
}
imds_aws_api_version_imdsv1_body() {
# IMDSv1 (2009-04-04) should not attempt to get a token
# Set IMDS_API_VERSION before fake_metadata so WGET_STRIP_PREFIX is correct
IMDS_API_VERSION=2009-04-04 CLOUD=aws fake_metadata aws <<-EOF
hostname: test-imdsv1
EOF
# For IMDSv1, nc should not be called at all
# If it is called, the test will fail because we're not mocking it
IMDS_API_VERSION=2009-04-04 CLOUD=aws atf_check \
-o match:"test-imdsv1" \
imds @hostname
}
imds_aws_api_version_imdsv2_explicit_body() {
# IMDSv2 with explicit version (2009-04-05 or later)
# Verify that metadata can be retrieved with explicit API version
# Set IMDS_API_VERSION before fake_metadata so WGET_STRIP_PREFIX is correct
IMDS_API_VERSION=2009-04-05 CLOUD=aws fake_metadata aws <<-EOF
hostname: test-imdsv2-explicit
EOF
# Mock nc to provide a token (for IMDSv2 token request)
fake_bin nc <<-'NCEOF'
#!/bin/sh
cat > /dev/null
printf "HTTP/1.0 200 OK\r\n\r\nmock-token"
NCEOF
IMDS_API_VERSION=2009-04-05 CLOUD=aws atf_check \
-o match:"test-imdsv2-explicit" \
imds @hostname
}
imds_aws_api_version_imdsv2_latest_body() {
# IMDSv2 with 'latest' (default behavior)
# Verify that metadata can be retrieved with latest API version
# Set IMDS_API_VERSION before fake_metadata so WGET_STRIP_PREFIX is correct
IMDS_API_VERSION=latest CLOUD=aws fake_metadata aws <<-EOF
hostname: test-imdsv2-latest
EOF
# Mock nc to provide a token (for IMDSv2 token request)
fake_bin nc <<-'NCEOF'
#!/bin/sh
cat > /dev/null
printf "HTTP/1.0 200 OK\r\n\r\nmock-token"
NCEOF
IMDS_API_VERSION=latest CLOUD=aws atf_check \
-o match:"test-imdsv2-latest" \
imds @hostname
}
imds_nocloud_cmdline_local_hostname_body() {
atf_require_prog yx
mkdir proc

View File

@ -11,6 +11,8 @@ lib="$srcdir"/lib/tiny-cloud/init
init_tests \
expand_root \
expand_root_partition \
expand_root_lvm_partition \
expand_root_lvm_whole_disk \
ethernets \
find_first_interface_up \
auto_detect_ethernet_interface \
@ -69,6 +71,75 @@ expand_root_partition_body() {
done
}
expand_root_lvm_partition_body() {
# Test LVM on a partition (e.g., /dev/sda2 -> PV -> VG -> LV)
mkdir -p proc sys/class/block \
sys/devices/pci0000:00/0000:00:05.0/virtio2/block/vda/vda2 \
sys/devices/pci0000:00/0000:00:05.0/virtio2/block/vda/device \
dev/mapper
ln -s ../../devices/pci0000:00/0000:00:05.0/virtio2/block/vda sys/class/block/vda
ln -s ../../devices/pci0000:00/0000:00:05.0/virtio2/block/vda/vda2 sys/class/block/vda2
echo 2 > sys/class/block/vda2/partition
# Create fake LVM device
mkdir -p sys/class/block/dm-0
ln -s /dev/mapper/vg0-root dev/mapper/vg0-root
# Mock LVM commands
fake_bin lvs <<-EOF
#!/bin/sh
echo " vg0"
EOF
fake_bin pvs <<-EOF
#!/bin/sh
echo " /dev/vda2"
EOF
echo "/dev/mapper/vg0-root / ext4 rw,noatime 0 0" > proc/mounts
for provider in $PROVIDERS; do
CLOUD="$provider" atf_check \
-o match:"sfdisk .*/dev/vda" \
-o match:"partx .*/dev/vda" \
-o match:"pvresize /dev/vda2" \
-o match:"lvextend -l \\+85%VG /dev/mapper/vg0-root" \
-o match:"resize2fs /dev/mapper/vg0-root" \
sh -c ". $lib; init__expand_root"
done
}
expand_root_lvm_whole_disk_body() {
# Test LVM on whole disk (e.g., /dev/sdb -> PV -> VG -> LV, no partition)
mkdir -p proc sys/class/block \
sys/devices/pci0000:00/0000:00:05.0/virtio3/block/vdb/device \
dev/mapper
ln -s ../../devices/pci0000:00/0000:00:05.0/virtio3/block/vdb sys/class/block/vdb
# Create fake LVM device
mkdir -p sys/class/block/dm-1
ln -s /dev/mapper/vg1-root dev/mapper/vg1-root
# Mock LVM commands - PV is on whole disk (no partition)
fake_bin lvs <<-EOF
#!/bin/sh
echo " vg1"
EOF
fake_bin pvs <<-EOF
#!/bin/sh
echo " /dev/vdb"
EOF
echo "/dev/mapper/vg1-root / ext4 rw,noatime 0 0" > proc/mounts
for provider in $PROVIDERS; do
CLOUD="$provider" atf_check \
-o match:"pvresize /dev/vdb" \
-o match:"lvextend -l \\+85%VG /dev/mapper/vg1-root" \
-o match:"resize2fs /dev/mapper/vg1-root" \
-o not-match:"sfdisk" \
-o not-match:"partx" \
sh -c ". $lib; init__expand_root"
done
}
ethernets_body() {
fake_interfaces lo br0 eth0 eth2 eth11

View File

@ -66,7 +66,7 @@ fake_userdata_nocloud() {
fake_metadata_aws() {
cat > "169.254.169.254.yaml"
export WGET_STRIP_PREFIX="/latest/meta-data"
export WGET_STRIP_PREFIX="/${IMDS_API_VERSION:-latest}/meta-data"
}
fake_metadata_azure() {

View File

@ -30,6 +30,8 @@ init_tests \
userdata_users_system \
userdata_users_no_create_home \
userdata_users_groups \
userdata_users_passwd \
userdata_users_hashed_passwd \
userdata_users_lock_passwd \
userdata_users_doas \
userdata_users_doas_with_default \
@ -385,6 +387,38 @@ userdata_users_groups_body() {
tiny-cloud main
}
userdata_users_passwd_body() {
# first specified user will replace default user
fake_userdata_nocloud <<-EOF
#alpine-config
users:
- none
- name: foo
passwd: $6$foosalt$QuhZ.r54aqCAn7mTnU4jBh9LPyuVQCa8.H0dZWCMYHVaNzsPX/heqKqI3EtnB6j.YLuaENmnlEHTiwu.iVVcG1
EOF
atf_check -e ignore -o ignore tiny-cloud early
atf_check \
-e match:"userdata_users: done" \
-o match:"chpasswd -e" \
tiny-cloud main
}
userdata_users_hashed_passwd_body() {
# first specified user will replace default user
fake_userdata_nocloud <<-EOF
#alpine-config
users:
- none
- name: foo
hashed_passwd: $6$foosalt$QuhZ.r54aqCAn7mTnU4jBh9LPyuVQCa8.H0dZWCMYHVaNzsPX/heqKqI3EtnB6j.YLuaENmnlEHTiwu.iVVcG1
EOF
atf_check -e ignore -o ignore tiny-cloud early
atf_check \
-e match:"userdata_users: done" \
-o match:"chpasswd -e" \
tiny-cloud main
}
userdata_users_lock_passwd_body() {
# first specified user will replace default user
fake_userdata_nocloud <<-EOF