[Proxmox] - How to create VM template with a user that require password for sudo

Hello everyone,

Few days ago, I watched this tutorial by Jay on how to create a Proxmox VM template. I followed all the steps and I can confirm it’s working fine.

But, the Linux user (I defined this user name and password in the Proxmox Cloud-Init section) comes with passwordless sudo access. I wonder if it’s possible to change this behaviour a little bit - I want the password prompt to appear when sudo <command> is executed.

From what I found, this can be done via Cloud-Init itself. Having said that, I then tried the “Proxmox’s --cicustom” steps mentioned in this repository but it didn’t work. The Linux user still has passwordless sudo access.

Is this doable?

Appreciate any form of inputs/links/snippets from fellow members of this forum on how to achieve this. Thank you in advance.

Welcome to the forum!

I don’t use cloud-init, so I’m not familiar with its quirks. I tried to read about the group config a bit and there’s none. It’s too limited. I think best workaround you can do is add a group, add the user to the group, skip the sudo part of cloud-init and write a file using cloud-init.

  - myadmins

  - name: foobar
    gecos: probably optional
    primary_group: foobar
    selinux_user: staff_u
    groups: myadmins

  - path: /etc/sudoers
    content: |
      %myadmins ALL=(ALL:ALL) ALL
    owner: "root:root"
    permissions: "0660"
    append: true

  - "groupmod -g 1234 myadmins"
  - "usermod -u 1000 foobar"
  - "groupmod -g 1000 foobar"
  - "chmod -R foobar:foobar /home/foobar"

This is by no means complete, just an example I made up. Add your user’s creation config template, but without adding the sudo: part. Sudo should be done from the content from the sudoers file, at the group level, group of which your user should now be part of.

The runcmd part is optional, but it’s a good idea to have your groups created with higher GIDs, because when you create new users on the system, they’ll (most of the time) get their own groups with the same GID. If you have a group without a user on the system, then you will have a mismatch between the user and gid. It’s not bad, but annoying to see on any system. So I made the gid of myadmins higher and made sure to set the uid of the user foobar to 1000 and the gid of the group foobar to 1000 as well (1000 is generally the ID that they will be created with).


I find cloud-init to be such an inconvenience. It has weird quirks, like not having to start the VM before you config this and other stuff like missing group configs for sudo and more. And it has a CMD option, because it is limited. If I wanted to write a bash script to initialize my system, I would have wrote it already. Actually, that would be what I’d be doing if I were to deploy many systems at once.

Instead of using cloud-init, I preferred to just create a VM, set it up with not just the main users, but also the software necessary for it to be a proper template, then convert it to a template. Before, I didn’t know each VM had a unique machine ID and they’d be cloned with the template, but all you have to do is remove the machine id and run a command, which can be used when initializing the VM.

We were cloning VMs on demand, generally not more than 2 VMs at a time. The IP address was static on the VM (because DHCP is not to be trusted) and the test IP was always unused. When the VMs booted, we changed it with something available (the only reason DHCP existed for the VM VLAN was to keep track of assigned IPs). I can see how this would not be ideal with cloning tens of VMs at once, you’d immediately get IP conflicts if you don’t use DHCP. But by then, you need a better automation tool, like ansible and have a discovery mechanisms for new VMs, to have them automatically configured.

Cloud-init just feels so limiting to me. I don’t judge people using it, if it works for you, then great! But I would like to understand why is it that people choose to use it.

I’m hoping I’m not too late, and this may be of assistance to you. What will follow at the end of this response is one of the cloud-init config files I actually use. I generalized some information, and this one in particular has some things that are specific to Raspberry Pi, but you should at least be able to use it as a reference for how I achieved exactly what you’re looking for.

Also, some of the commands at the end won’t work for other people because that’s how I bootstrap Ansible, but I left it there for amusement. If you look through it, it should be fairly obvious what you’d have to change but let me know if you’re at all confused.

# The top level settings are used as module
# and system configuration.

# A set of users which may be applied and/or used by various modules
# when a 'default' entry is found it will reference the 'default_user'
# from the distro configuration specified below
 - name: jay
   lock_passwd: False
   passwd: <password_hash_here
   gecos: Jay
     - <ssh key here>
   groups: [adm, audio, cdrom, dialout, dip, floppy, lxd, netdev, plugdev, sudo, video]
   sudo: ["ALL=(ALL) NOPASSWD:ALL"]
   shell: /bin/bash

# If this is set, 'root' will not be able to ssh in and they
# will get a message to login instead as the default $user
disable_root: true

# This will cause the set+update hostname module to not operate (if true)
hostname: k8s-controller.home-network.io
preserve_hostname: false
manage_etc_hosts: true

# The modules that run in the 'init' stage
 - migrator
 - seed_random
 - bootcmd
 - write-files
 - growpart
 - resizefs
 - disk_setup
 - mounts
 - set_hostname
 - update_hostname
 - update_etc_hosts
 - ca-certs
 - rsyslog
 - users-groups
 - ssh

# The modules that run in the 'config' stage
# Emit the cloud config ready event
# this can be used by upstart jobs for 'start on cloud-config'.
 - emit_upstart
 - snap
 - ssh-import-id
 - locale
 - set-passwords
 - grub-dpkg
 - apt-pipelining
 - apt-configure
 - ubuntu-advantage
 - ntp
 - timezone: "America/Detroit"
 - runcmd

# The modules that run in the 'final' stage
 - package-update-upgrade-install
 - fan
 - lxd
 - ubuntu-drivers
 - reset_rmc
 - refresh_rmc_and_interface
 - rightscale_userdata
 - scripts-vendor
 - scripts-per-once
 - scripts-per-boot
 - scripts-per-instance
 - scripts-user
 - ssh-authkey-fingerprints
 - keys-to-console
 - phone-home
 - final-message
 - power-state-change
 - packages

# System and/or distro specific settings
# (not accessible to handlers/transforms)
   # This will affect which distro class gets used
   distro: ubuntu
   # Default user name + that default users groups (if added/used)
     renderers: ['netplan', 'eni', 'sysconfig']
   # Automatically discover the best ntp_client
   ntp_client: auto
   # Other config here will be given to the distro class and/or path classes
      cloud_dir: /var/lib/cloud/
      templates_dir: /etc/cloud/templates/
      upstart_dir: /etc/init/
     - arches: [i386, amd64]
         primary: http://archive.ubuntu.com/ubuntu
         security: http://security.ubuntu.com/ubuntu
           - http://%(ec2_region)s.ec2.archive.ubuntu.com/ubuntu/
           - http://%(availability_zone)s.clouds.archive.ubuntu.com/ubuntu/
           - http://%(region)s.clouds.archive.ubuntu.com/ubuntu/
         security: []
     - arches: [arm64, armel, armhf]
         primary: http://ports.ubuntu.com/ubuntu-ports
         security: http://ports.ubuntu.com/ubuntu-ports
           - http://%(ec2_region)s.ec2.ports.ubuntu.com/ubuntu-ports/
           - http://%(availability_zone)s.clouds.ports.ubuntu.com/ubuntu-ports/
           - http://%(region)s.clouds.ports.ubuntu.com/ubuntu-ports/
         security: []
     - arches: [default]
         primary: http://ports.ubuntu.com/ubuntu-ports
         security: http://ports.ubuntu.com/ubuntu-ports
   ssh_svcname: ssh

  - echo $DATE >> /etc/birth_certificate

  - git
  - tmux
  - vim-nox

  - mkdir /var/log/ansible
  - curl deploy/bootstrap | bash