{ config, lib, pkgs, ... }: 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 = lib.mdDoc "Whether to enable the Windows Azure Linux Agent."; }; verboseLogging = mkOption { default = false; description = lib.mdDoc "Whether to enable verbose logging."; }; mountResourceDisk = mkOption { default = true; description = lib.mdDoc "Whether the agent should format (ext4) and mount the resource disk to /mnt/resource."; }; }; ###### implementation config = lib.mkIf cfg.enable { assertions = [{ assertion = pkgs.stdenv.hostPlatform.isx86; message = "Azure not currently supported on ${pkgs.stdenv.hostPlatform.system}"; } { 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 = '' 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" "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 ]; 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 ''; }; }