Skip to content

Instantly share code, notes, and snippets.

@djoreilly
Last active May 17, 2023 08:04
Show Gist options
  • Save djoreilly/e31ade0f9684160d9c81cec07791859c to your computer and use it in GitHub Desktop.
Save djoreilly/e31ade0f9684160d9c81cec07791859c to your computer and use it in GitHub Desktop.
Metadata server to allow booting cloud-init images on Libvirt
"""
Server to answer requests from Libvirt VMs to http://169.254.169.254/
Cloud images usually don't have a preset user/password, and this is needed to add a ssh pub key to .ssh/authorized_hosts.
Change SSH_PUB_KEY path below.
pip install bottle
sudo ip address add 169.254.169.254 dev virbr0
open firewall
-A ufw-user-input -s 192.168.122.0/24 -d 169.254.169.254/32 -i virbr0 -p tcp -m tcp --dport 80 -j ACCEPT
sudo python md_svr.py
--- OR ---
start server without sudo on 192.168.122.1:8080 and no need for 169 ip on virbr0.
iptables -t nat -A PREROUTING -i virbr0 -p tcp -m tcp -d 169.254.169.254 --dport 80 -j DNAT --to-destination 192.168.122.1:8080
iptables -t filter -A ufw-user-input -i virbr0 -p tcp -d 192.168.122.1 --dport 8080 -s 192.168.122.0/24 -j ACCEPT
change two lines at bottom of script
python md_svr.py
"""
import os
import json
from bottle import route, run, request
SSH_PUB_KEY = '/home/doreilly/.ssh/id_rsa.pub'
DNS_PATH = '/var/lib/libvirt/dnsmasq/'
@route('/')
def root():
# print dict(request.headers)
return "2009-04-04/"
@route('/2009-04-04/meta-data/')
def metadata():
return 'instance-id\nhostname\npublic-keys/'
@route('/2009-04-04/meta-data/hostname')
def hostname():
"""Return the libvirt domain name from dnsmasq files"""
client_ip = request.get('REMOTE_ADDR')
with open(os.path.join(DNS_PATH, "virbr0.status")) as f:
for entry in json.load(f):
if entry.get('ip-address') == client_ip:
mac_addr = entry.get('mac-address')
break
else:
return
with open(os.path.join(DNS_PATH, "virbr0.macs")) as f:
for entry in json.load(f):
if mac_addr in entry.get('macs'):
return entry.get('domain')
@route('/2009-04-04/meta-data/instance-id')
def instance_id():
return "i-%s" % request.get('REMOTE_ADDR')
@route('/2009-04-04/meta-data/public-keys')
@route('/2009-04-04/meta-data/public-keys/')
def pub_keys():
return '0=default'
@route('/2009-04-04/meta-data/public-keys/0/')
def pub_key0():
return 'openssh-key'
@route('/2009-04-04/meta-data/public-keys/0/openssh-key')
def openssh_key():
with open(SSH_PUB_KEY) as f:
return f.read()
run(host='169.254.169.254', port=80, debug=True)
# comment previous line and uncomment next if you are natting the address and port
# run(host='192.168.122.1', port=8080, debug=True)
@derit
Copy link

derit commented Jan 3, 2021

how it work?
i'm using virsh install, have an example?

@djoreilly
Copy link
Author

how it work?
i'm using virsh install, have an example?

The VM needs cloud-init enabled in the image. When it boots, it calls the service to get the public ssh key, and puts it into .ssh/authorized_keys - so now you can ssh to the VM from the host.

$ wget https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img
$ mount-image-callback focal-server-cloudimg-amd64.img -- sh -c 'echo "datasource: Ec2" > $MOUNTPOINT/etc/cloud/ds-identify.cfg'
$ virsh .... # boot new VM from image, or use image as backing volume
$ ssh ubuntu@vm-ip

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment