waagent: init module

This commit is contained in:
codgician 2024-12-06 23:32:16 +08:00
parent 19941080a5
commit 0d7c515332
No known key found for this signature in database
7 changed files with 502 additions and 291 deletions

View File

@ -70,6 +70,8 @@
- `nodePackages.ganache` has been removed, as the package has been deprecated by upstream.
- `services.waagent` option has been added, making `/etc/waagent.conf` customizable. `virtualisation.azure.agent` option provided by `azure-agent.nix` module has been deprecated and the module will be removed in a future release.
- `containerd` has been updated to v2, which contains breaking changes. See the [containerd
2.0](https://github.com/containerd/containerd/blob/main/docs/containerd-2.0.md) documentation for more
details.

View File

@ -1757,6 +1757,7 @@
./virtualisation/virtualbox-host.nix
./virtualisation/vmware-guest.nix
./virtualisation/vmware-host.nix
./virtualisation/waagent.nix
./virtualisation/waydroid.nix
./virtualisation/xe-guest-utilities.nix
./virtualisation/xen-dom0.nix

View File

@ -1,291 +1,56 @@
{ config, lib, pkgs, ... }:
{ lib, ... }:
with lib;
let
cfg = config.virtualisation.azure.agent;
provisionedHook = pkgs.writeScript "provisioned-hook" ''
#!${pkgs.runtimeShell}
/run/current-system/systemd/bin/systemctl start provisioned.target
'';
in
{
###### interface
options.virtualisation.azure.agent = {
enable = mkOption {
default = false;
description = "Whether to enable the Windows Azure Linux Agent.";
};
verboseLogging = mkOption {
default = false;
description = "Whether to enable verbose logging.";
};
mountResourceDisk = mkOption {
default = true;
description = "Whether the agent should format (ext4) and mount the resource disk to /mnt/resource.";
};
};
###### implementation
config = lib.mkIf cfg.enable {
assertions = [{
assertion = config.networking.networkmanager.enable == false;
message = "Windows Azure Linux Agent is not compatible with NetworkManager";
}];
boot.initrd.kernelModules = [ "ata_piix" ];
networking.firewall.allowedUDPPorts = [ 68 ];
environment.etc."waagent.conf".text = ''
#
# Microsoft Azure Linux Agent Configuration
#
# Enable extension handling. Do not disable this unless you do not need password reset,
# backup, monitoring, or any extension handling whatsoever.
Extensions.Enabled=y
# How often (in seconds) to poll for new goal states
Extensions.GoalStatePeriod=6
# Which provisioning agent to use. Supported values are "auto" (default), "waagent",
# "cloud-init", or "disabled".
Provisioning.Agent=auto
# Password authentication for root account will be unavailable.
Provisioning.DeleteRootPassword=n
# Generate fresh host key pair.
Provisioning.RegenerateSshHostKeyPair=n
# Supported values are "rsa", "dsa", "ecdsa", "ed25519", and "auto".
# The "auto" option is supported on OpenSSH 5.9 (2011) and later.
Provisioning.SshHostKeyPairType=ed25519
# Monitor host name changes and publish changes via DHCP requests.
Provisioning.MonitorHostName=y
# How often (in seconds) to monitor host name changes.
Provisioning.MonitorHostNamePeriod=30
# Decode CustomData from Base64.
Provisioning.DecodeCustomData=n
# Execute CustomData after provisioning.
Provisioning.ExecuteCustomData=n
# Algorithm used by crypt when generating password hash.
#Provisioning.PasswordCryptId=6
# Length of random salt used when generating password hash.
#Provisioning.PasswordCryptSaltLength=10
# Allow reset password of sys user
Provisioning.AllowResetSysUser=n
# Format if unformatted. If 'n', resource disk will not be mounted.
ResourceDisk.Format=${if cfg.mountResourceDisk then "y" else "n"}
# File system on the resource disk
# Typically ext3 or ext4. FreeBSD images should use 'ufs2' here.
ResourceDisk.Filesystem=ext4
# Mount point for the resource disk
ResourceDisk.MountPoint=/mnt/resource
# Create and use swapfile on resource disk.
ResourceDisk.EnableSwap=n
# Size of the swapfile.
ResourceDisk.SwapSizeMB=0
# Comma-separated list of mount options. See mount(8) for valid options.
ResourceDisk.MountOptions=None
# Enable verbose logging (y|n)
Logs.Verbose=${if cfg.verboseLogging then "y" else "n"}
# Enable Console logging, default is y
# Logs.Console=y
# Enable periodic log collection, default is n
Logs.Collect=n
# How frequently to collect logs, default is each hour
Logs.CollectPeriod=3600
# Is FIPS enabled
OS.EnableFIPS=n
# Root device timeout in seconds.
OS.RootDeviceScsiTimeout=300
# How often (in seconds) to set the root device timeout.
OS.RootDeviceScsiTimeoutPeriod=30
# If "None", the system default version is used.
OS.OpensslPath=${pkgs.openssl_3.bin}/bin/openssl
# Set the SSH ClientAliveInterval
# OS.SshClientAliveInterval=180
# Set the path to SSH keys and configuration files
OS.SshDir=/etc/ssh
# If set, agent will use proxy server to access internet
#HttpProxy.Host=None
#HttpProxy.Port=None
# Detect Scvmm environment, default is n
# DetectScvmmEnv=n
#
# Lib.Dir=/var/lib/waagent
#
# DVD.MountPoint=/mnt/cdrom/secure
#
# Pid.File=/var/run/waagent.pid
#
# Extension.LogDir=/var/log/azure
#
# Home.Dir=/home
# Enable RDMA management and set up, should only be used in HPC images
OS.EnableRDMA=n
# Enable checking RDMA driver version and update
# OS.CheckRdmaDriver=y
# Enable or disable goal state processing auto-update, default is enabled
AutoUpdate.Enabled=n
# Determine the update family, this should not be changed
# AutoUpdate.GAFamily=Prod
# Determine if the overprovisioning feature is enabled. If yes, hold extension
# handling until inVMArtifactsProfile.OnHold is false.
# Default is enabled
EnableOverProvisioning=n
# Allow fallback to HTTP if HTTPS is unavailable
# Note: Allowing HTTP (vs. HTTPS) may cause security risks
# OS.AllowHTTP=n
# Add firewall rules to protect access to Azure host node services
OS.EnableFirewall=n
# How often (in seconds) to check the firewall rules
OS.EnableFirewallPeriod=30
# How often (in seconds) to remove the udev rules for persistent network interface
# names (75-persistent-net-generator.rules and /etc/udev/rules.d/70-persistent-net.rules)
OS.RemovePersistentNetRulesPeriod=30
# How often (in seconds) to monitor for DHCP client restarts
OS.MonitorDhcpClientRestartPeriod=30
'';
services.udev.packages = [ pkgs.waagent ];
# Provide waagent-shipped udev rules in initrd too.
boot.initrd.services.udev.packages = [ pkgs.waagent ];
# udev rules shell out to chmod, cut and readlink, which are all
# provided by pkgs.coreutils, which is in services.udev.path, but not
# boot.initrd.services.udev.binPackages.
boot.initrd.services.udev.binPackages = [ pkgs.coreutils ];
networking.dhcpcd.persistent = true;
services.logrotate = {
enable = true;
settings."/var/log/waagent.log" = {
compress = true;
frequency = "monthly";
rotate = 6;
};
};
systemd.targets.provisioned = {
description = "Services Requiring Azure VM provisioning to have finished";
};
systemd.services.consume-hypervisor-entropy =
{
description = "Consume entropy in ACPI table provided by Hyper-V";
wantedBy = [ "sshd.service" "waagent.service" ];
before = [ "sshd.service" "waagent.service" ];
path = [ pkgs.coreutils ];
script =
warn
''
echo "Fetching entropy..."
cat /sys/firmware/acpi/tables/OEM0 > /dev/random
'';
serviceConfig.Type = "oneshot";
serviceConfig.RemainAfterExit = true;
serviceConfig.StandardError = "journal+console";
serviceConfig.StandardOutput = "journal+console";
};
`virtualisation.azure.agent` provided by `azure-agent.nix` module has been replaced
by `services.waagent` options, and will be removed in a future release.
''
{
systemd.services.waagent = {
wantedBy = [ "multi-user.target" ];
after = [ "network-online.target" "sshd.service" ];
wants = [ "network-online.target" ];
path = [
pkgs.e2fsprogs
pkgs.bash
pkgs.findutils
pkgs.gnugrep
pkgs.gnused
pkgs.iproute2
pkgs.iptables
# for hostname
pkgs.nettools
pkgs.openssh
pkgs.openssl
pkgs.parted
# for pidof
pkgs.procps
# for useradd, usermod
pkgs.shadow
pkgs.util-linux # for (u)mount, fdisk, sfdisk, mkswap
# waagent's Microsoft.OSTCExtensions.VMAccessForLinux needs Python 3
pkgs.python39
# waagent's Microsoft.CPlat.Core.RunCommandLinux needs lsof
pkgs.lsof
imports = [
(mkRenamedOptionModule
[
"virtualisation"
"azure"
"agent"
"enable"
]
[
"services"
"waagent"
"enable"
]
)
(mkRenamedOptionModule
[
"virtualisation"
"azure"
"agent"
"verboseLogging"
]
[
"services"
"waagent"
"settings"
"Logs"
"Verbose"
]
)
(mkRenamedOptionModule
[
"virtualisation"
"azure"
"agent"
"mountResourceDisk"
]
[
"services"
"waagent"
"settings"
"ResourceDisk"
"Format"
]
)
];
description = "Windows Azure Agent Service";
unitConfig.ConditionPathExists = "/etc/waagent.conf";
serviceConfig = {
ExecStart = "${pkgs.waagent}/bin/waagent -daemon";
Type = "simple";
};
};
# waagent will generate files under /etc/sudoers.d during provisioning
security.sudo.extraConfig = ''
#includedir /etc/sudoers.d
'';
};
}

View File

@ -0,0 +1,364 @@
{
config,
lib,
pkgs,
...
}:
with lib;
let
cfg = config.services.waagent;
# Format for waagent.conf
settingsFormat = {
type =
with types;
let
singleAtom =
(nullOr (oneOf [
bool
str
int
float
]))
// {
description = "atom (bool, string, int or float) or null";
};
atom = either singleAtom (listOf singleAtom) // {
description = singleAtom.description + " or a list of them";
};
in
attrsOf (
either atom (attrsOf atom)
// {
description = atom.description + "or an attribute set of them";
}
);
generate =
name: value:
let
# Transform non-attribute values
transform =
x:
# Transform bool to "y" or "n"
if (isBool x) then
(if x then "y" else "n")
# Concatenate list items with comma
else if (isList x) then
concatStringsSep "," (map transform x)
else
toString x;
# Convert to format of waagent.conf
recurse =
path: value:
if builtins.isAttrs value then
pipe value [
(mapAttrsToList (k: v: recurse (path ++ [ k ]) v))
concatLists
]
else
[
{
name = concatStringsSep "." path;
inherit value;
}
];
convert =
attrs:
pipe (recurse [ ] attrs) [
# Filter out null values and emoty lists
(filter (kv: kv.value != null && kv.value != [ ]))
# Transform to Key=Value form, then concatenate
(map (kv: "${kv.name}=${transform kv.value}"))
(concatStringsSep "\n")
];
in
pkgs.writeText name (convert value);
};
settingsType = types.submodule {
freeformType = settingsFormat.type;
options = {
Provisioning = {
Enable = mkOption {
type = types.bool;
default = !config.services.cloud-init.enable;
defaultText = literalExpression "!config.services.cloud-init.enable";
description = ''
Whether to enable provisioning functionality in the agent.
If provisioning is disabled, SSH host and user keys in the image are preserved
and configuration in the Azure provisioning API is ignored.
Set to `false` if cloud-init is used for provisioning tasks.
'';
};
Agent = mkOption {
type = types.enum [
"auto"
"waagent"
"cloud-init"
"disabled"
];
default = "auto";
description = ''
Which provisioning agent to use.
'';
};
};
ResourceDisk = {
Format = mkEnableOption ''
If set to `true`, waagent formats and mounts the resource disk that the platform provides,
unless the file system type in `ResourceDisk.FileSystem` is set to `ntfs`.
The agent makes a single Linux partition (ID 83) available on the disk.
This partition isn't formatted if it can be successfully mounted.
This configuration has no effect if resource disk is managed by cloud-init.
'';
FileSystem = mkOption {
type = types.str;
default = "ext4";
description = ''
The file system type for the resource disk.
If the string is `X`, then `mkfs.X` should be present in the environment.
You can add additional filesystem packages using `services.waagent.extraPackages`.
This configuration has no effect if resource disk is managed by cloud-init.
'';
};
MountPoint = mkOption {
type = types.str;
default = "/mnt/resource";
description = ''
This option specifies the path at which the resource disk is mounted.
The resource disk is a temporary disk and might be emptied when the VM is deprovisioned.
This configuration has no effect if resource disk is managed by cloud-init.
'';
};
MountOptions = mkOption {
type = with types; listOf str;
default = [ ];
example = [
"nodev"
"nosuid"
];
description = ''
This option specifies disk mount options to be passed to the `mount -o` command.
For more information, see the `mount(8)` manual page.
'';
};
EnableSwap = mkEnableOption ''
If enabled, the agent creates a swap file (`/swapfile`) on the resource disk
and adds it to the system swap space.
This configuration has no effect if resource disk is managed by cloud-init.
'';
SwapSizeMB = mkOption {
type = types.int;
default = 0;
description = ''
Specifies the size of the swap file in megabytes.
This configuration has no effect if resource disk is managed by cloud-init.
'';
};
};
Logs.Verbose = lib.mkEnableOption ''
If you set this option, log verbosity is boosted.
Waagent logs to `/var/log/waagent.log` and uses the system logrotate functionality to rotate logs.
'';
OS = {
EnableRDMA = lib.mkEnableOption ''
If enabled, the agent attempts to install and then load an RDMA kernel driver
that matches the version of the firmware on the underlying hardware.
'';
RootDeviceScsiTimeout = lib.mkOption {
type = types.nullOr types.int;
default = 300;
description = ''
Configures the SCSI timeout in seconds on the OS disk and data drives.
If set to `null`, the system defaults are used.
'';
};
};
HttpProxy = {
Host = lib.mkOption {
type = types.nullOr types.str;
default = null;
description = ''
If you set http proxy, waagent will use is proxy to access the Internet.
'';
};
Port = lib.mkOption {
type = types.nullOr types.int;
default = null;
description = ''
If you set http proxy, waagent will use this proxy to access the Internet.
'';
};
};
AutoUpdate.Enable = lib.mkEnableOption ''
Enable or disable autoupdate for goal state processing.
'';
};
};
in
{
options.services.waagent = {
enable = lib.mkEnableOption ''
Whether to enable the Windows Azure Linux Agent.
'';
package = lib.mkPackageOption pkgs "waagent" { };
extraPackages = lib.mkOption {
default = [ ];
description = ''
Additional packages to add to the waagent {env}`PATH`.
'';
example = lib.literalExpression "[ pkgs.powershell ]";
type = lib.types.listOf lib.types.package;
};
settings = lib.mkOption {
type = settingsType;
default = { };
description = ''
The waagent.conf configuration, see https://learn.microsoft.com/en-us/azure/virtual-machines/extensions/agent-linux for documentation.
'';
};
};
config = lib.mkIf cfg.enable {
assertions = [
{
assertion = cfg.settings.HttpProxy.Host == null || cfg.settings.HttpProxy.Port != null;
message = "Option services.waagent.settings.HttpProxy.Port must be set if services.waagent.settings.HttpProxy.Host is set.";
}
];
boot.initrd.kernelModules = [ "ata_piix" ];
networking.firewall.allowedUDPPorts = [ 68 ];
services.udev.packages = with pkgs; [ waagent ];
boot.initrd.services.udev = with pkgs; {
# Provide waagent-shipped udev rules in initrd too.
packages = [ waagent ];
# udev rules shell out to chmod, cut and readlink, which are all
# provided by pkgs.coreutils, which is in services.udev.path, but not
# boot.initrd.services.udev.binPackages.
binPackages = [ coreutils ];
};
networking.dhcpcd.persistent = true;
services.logrotate = {
enable = true;
settings."/var/log/waagent.log" = {
compress = true;
frequency = "monthly";
rotate = 6;
};
};
# Write settings to /etc/waagent.conf
environment.etc."waagent.conf".source = settingsFormat.generate "waagent.conf" cfg.settings;
systemd.targets.provisioned = {
description = "Services Requiring Azure VM provisioning to have finished";
};
systemd.services.consume-hypervisor-entropy = {
description = "Consume entropy in ACPI table provided by Hyper-V";
wantedBy = [
"sshd.service"
"waagent.service"
];
before = [
"sshd.service"
"waagent.service"
];
path = [ pkgs.coreutils ];
script = ''
echo "Fetching entropy..."
cat /sys/firmware/acpi/tables/OEM0 > /dev/random
'';
serviceConfig.Type = "oneshot";
serviceConfig.RemainAfterExit = true;
serviceConfig.StandardError = "journal+console";
serviceConfig.StandardOutput = "journal+console";
};
systemd.services.waagent = {
wantedBy = [ "multi-user.target" ];
after = [
"network-online.target"
] ++ lib.optionals config.services.cloud-init.enable [ "cloud-init.service" ];
wants = [
"network-online.target"
"sshd.service"
"sshd-keygen.service"
];
path =
with pkgs;
[
e2fsprogs
bash
findutils
gnugrep
gnused
iproute2
iptables
openssh
openssl
parted
# for hostname
nettools
# for pidof
procps
# for useradd, usermod
shadow
util-linux # for (u)mount, fdisk, sfdisk, mkswap
# waagent's Microsoft.CPlat.Core.RunCommandLinux needs lsof
lsof
]
++ cfg.extraPackages;
description = "Windows Azure Agent Service";
unitConfig.ConditionPathExists = "/etc/waagent.conf";
serviceConfig = {
ExecStart = "${lib.getExe cfg.package} -daemon";
Type = "simple";
Restart = "always";
Slice = "azure.slice";
CPUAccounting = true;
MemoryAccounting = true;
};
};
# waagent will generate files under /etc/sudoers.d during provisioning
security.sudo.extraConfig = ''
#includedir /etc/sudoers.d
'';
};
}

View File

@ -1114,6 +1114,7 @@ in {
vscode-remote-ssh = handleTestOn ["x86_64-linux"] ./vscode-remote-ssh.nix {};
vscodium = discoverTests (import ./vscodium.nix);
vsftpd = handleTest ./vsftpd.nix {};
waagent = handleTest ./waagent.nix {};
wakapi = handleTest ./wakapi.nix {};
warzone2100 = handleTest ./warzone2100.nix {};
wasabibackend = handleTest ./wasabibackend.nix {};

72
nixos/tests/waagent.nix Normal file
View File

@ -0,0 +1,72 @@
import ./make-test-python.nix (
{ lib, pkgs, ... }:
let
confPath = "/etc/waagent.conf";
in
{
name = "waagent";
meta = {
maintainers = with lib.maintainers; [ codgician ];
};
nodes.machine = {
services.waagent = {
enable = true;
settings = {
Provisioning = {
Enable = false;
Agent = "waagent";
DeleteRootPassword = false;
RegenerateSshHostKeyPair = false;
SshHostKeyPairType = "ed25519";
MonitorHostName = false;
};
ResourceDisk = {
Format = false;
MountOptions = [
"compress=lzo"
"mode=0600"
];
};
OS.RootDeviceScsiTimeout = 300;
HttpProxy = {
Host = null;
Port = null;
};
CGroups = {
EnforceLimits = false;
Excluded = [ ];
};
};
};
};
testScript = ''
# Defined values should be reflected in waagent.conf
machine.succeed("grep -q '^Provisioning.Enable=n$' '${confPath}'")
machine.succeed("grep -q '^Provisioning.Agent=waagent$' '${confPath}'")
machine.succeed("grep -q '^Provisioning.DeleteRootPassword=n$' '${confPath}'")
machine.succeed("grep -q '^Provisioning.RegenerateSshHostKeyPair=n$' '${confPath}'")
machine.succeed("grep -q '^Provisioning.SshHostKeyPairType=ed25519$' '${confPath}'")
machine.succeed("grep -q '^Provisioning.MonitorHostName=n$' '${confPath}'")
machine.succeed("grep -q '^ResourceDisk.Format=n$' '${confPath}'")
machine.succeed("grep -q '^ResourceDisk.MountOptions=compress=lzo,mode=0600$' '${confPath}'")
machine.succeed("grep -q '^OS.RootDeviceScsiTimeout=300$' '${confPath}'")
# Undocumented options should also be supported
machine.succeed("grep -q '^CGroups.EnforceLimits=n$' '${confPath}'")
# Null values should be skipped and not exist in waagent.conf
machine.fail("grep -q '^HttpProxy.Host=' '${confPath}'")
machine.fail("grep -q '^HttpProxy.Port=' '${confPath}'")
# Empty lists should be skipped and not exist in waagent.conf
machine.fail("grep -q '^CGroups.Excluded=' '${confPath}'")
# Test service start
# Skip testing actual functionality due to lacking Azure infrasturcture
machine.wait_for_unit("waagent.service")
'';
}
)

View File

@ -4,6 +4,7 @@
lib,
python3,
bash,
gitUpdater,
}:
let
@ -63,7 +64,11 @@ python.pkgs.buildPythonApplication rec {
dontWrapPythonPrograms = false;
meta = {
passthru.updateScript = gitUpdater {
rev-prefix = "v";
};
meta = with lib; {
description = "Microsoft Azure Linux Agent (waagent)";
mainProgram = "waagent";
longDescription = ''
@ -71,6 +76,7 @@ python.pkgs.buildPythonApplication rec {
manages Linux provisioning and VM interaction with the Azure
Fabric Controller'';
homepage = "https://github.com/Azure/WALinuxAgent";
license = with lib.licenses; [ asl20 ];
maintainers = with maintainers; [ codgician ];
license = with licenses; [ asl20 ];
};
}