Last active
February 18, 2022 08:18
-
-
Save dpneumo/a71473a38451223a524ad963b06dfb9e to your computer and use it in GitHub Desktop.
Modify cloud-init phone_home module to return a created server's public key for insertion into known_hosts. Works with cloud-iniit version 0.7.9 and later.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Diff of the original cc_phone_home and the slightly modified version that supports | |
# including the server pub_keys in the phone_home payload | |
# Original: https://github.com/number5/cloud-init/blob/master/cloudinit/config/cc_phone_home.py | |
# A couple of typos in comment lines in the original were elided to make the diff a bit clearer. | |
# The Centos7 distros I am using do not provide pub_key_dsa. | |
# I am talking to a Rails app with phone_home. | |
# A dummy X-CSRF-Token: 1234567890 in the headers simplifies the code on the Rails side. | |
# I authenticate the phone_home payload by including a token provided in the | |
# server user-data in the create request. | |
19d18 | |
< - ``pub_key_dsa`` | |
21a21 | |
> - ``pub_key_ed25519`` | |
35a36 | |
> token: abcd1234 | |
37c38 | |
< - pub_key_dsa | |
--- | |
> - pub_key_rsa | |
40a42 | |
> headers: {X-CSRF-Token: 1234567890} | |
42a45,46 | |
> import json | |
> | |
44d47 | |
< from cloudinit import url_helper | |
52d54 | |
< 'pub_key_dsa', | |
54a57 | |
> 'pub_key_ed25519', | |
67a71 | |
> # token: AbCd09876 | |
69a74,75 | |
> # headers: { X-CSRF-Token: 1234567890, | |
> # Content-Type: application/json } | |
70a77 | |
> | |
86a94 | |
> token = ph_cfg.get('token', '') | |
87a96 | |
> header_dict = ph_cfg.get('headers', {}) | |
107a117 | |
> 'pub_key_ed25519': '/etc/ssh/ssh_host_ed25519_key.pub' | |
138a149,155 | |
> | |
> # Assemble post data payload | |
> payload = real_submit_keys.copy() | |
> payload.update({ 'token': token }) | |
> serv_data = json.dumps({ 'serv_data': payload }) | |
> | |
> # Finally | |
140,142c157,160 | |
< url_helper.read_file_or_url( | |
< url, data=real_submit_keys, retries=tries, sec_between=3, | |
< ssl_details=util.fetch_ssl_details(cloud.paths)) | |
--- | |
> util.read_file_or_url(url, data=serv_data, | |
> retries=tries-1, sec_between=3, | |
> ssl_details=util.fetch_ssl_details(cloud.paths), | |
> headers=header_dict) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This controller receives the phone_home payload acquires the server ip address and writes the known_hosts entry | |
# The ServerSession class creates and remembers the token sent with the createserver request to DO | |
# DOServer handles the create server request and the query for the created server's ipaddress | |
# All requests to the DO api are via https so am making the assumption that responses are valid. | |
class DoHostkeysController < ApplicationController | |
protect_from_forgery except: :fingerprint | |
def fingerprint | |
raise RuntimeError, "Invalid ServerSession token was returned!" unless valid_server? | |
File.open(known_hosts,'a') {|f| f << "#{ipaddr} #{hostkey}" } | |
end | |
private | |
def hostkeys_params | |
permitted = params.require( "serv_data" ) | |
.permit( "token", "instance_id", "pub_key_rsa" ) | |
end | |
def valid_server? | |
ServerSession.valid_token?(hostkeys_params['token']) | |
end | |
def ipaddr | |
DoServer.new.get_ipaddr(hostkeys_params['instance_id']) | |
end | |
def hostkey | |
hostkeys_params['pub_key_rsa'] | |
end | |
def known_hosts | |
'/home/vagrant/.ssh/known_hosts' | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Rails routing for the do_hostkeys_controller | |
Rails.application.routes.draw do | |
# --- | |
post 'do_hostkeys/fingerprint', to: 'do_hostkeys#fingerprint', as: 'do_fingerprint' | |
# --- | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module UserDataConcern | |
def user_data | |
<<~USERDATA | |
#cloud-config | |
#{ ph_patch } | |
#{ phone_home } | |
USERDATA | |
end | |
def ph_patch | |
ph_path = '/usr/lib/python2.7/site-packages/cloudinit/config/cc_phone_home.py' | |
<<~PHPATCH | |
runcmd: | |
- mv #{ph_path} #{ph_path}.original | |
write_files: | |
- path: #{ph_path} | |
content: | | |
# Copyright (C) 2011 Canonical Ltd. | |
# Copyright (C) 2012, 2013 Hewlett-Packard Development Company, L.P. | |
# | |
# Author: Scott Moser <[email protected]> | |
# Author: Juerg Haefliger <[email protected]> | |
# | |
# This file is part of cloud-init. See LICENSE file for license information. | |
""" | |
Phone Home | |
---------- | |
**Summary:** post data to url | |
This module can be used to post data to a remote host after boot is complete. | |
If the post url contains the string ``$INSTANCE_ID`` it will be replaced with | |
the id of the current instance. Either all data can be posted or a list of | |
keys to post. Available keys are: | |
- ``pub_key_rsa`` | |
- ``pub_key_ecdsa`` | |
- ``pub_key_ed25519`` | |
- ``instance_id`` | |
- ``hostname`` | |
- ``fdqn`` | |
**Internal name:** ``cc_phone_home`` | |
**Module frequency:** per instance | |
**Supported distros:** all | |
**Config keys**:: | |
phone_home: | |
url: http://example.com/$INSTANCE_ID/ | |
token: abcd1234 | |
post: | |
- pub_key_rsa | |
- instance_id | |
- fqdn | |
tries: 10 | |
headers: {X-CSRF-Token: 1234567890} | |
""" | |
import json | |
from cloudinit import templater | |
from cloudinit import util | |
from cloudinit.settings import PER_INSTANCE | |
frequency = PER_INSTANCE | |
POST_LIST_ALL = [ | |
'pub_key_rsa', | |
'pub_key_ecdsa', | |
'pub_key_ed25519', | |
'instance_id', | |
'hostname', | |
'fqdn' | |
] | |
# phone_home: | |
# url: http://my.foo.bar/$INSTANCE_ID/ | |
# post: all | |
# tries: 10 | |
# | |
# phone_home: | |
# url: http://my.foo.bar/$INSTANCE_ID/ | |
# token: AbCd09876 | |
# post: [ pub_key_dsa, pub_key_rsa, pub_key_ecdsa, instance_id, hostname, | |
# fqdn ] | |
# headers: { X-CSRF-Token: 1234567890, | |
# Content-Type: application/json } | |
# | |
def handle(name, cfg, cloud, log, args): | |
if len(args) != 0: | |
ph_cfg = util.read_conf(args[0]) | |
else: | |
if 'phone_home' not in cfg: | |
log.debug(("Skipping module named %s, " | |
"no 'phone_home' configuration found"), name) | |
return | |
ph_cfg = cfg['phone_home'] | |
if 'url' not in ph_cfg: | |
log.warn(("Skipping module named %s, " | |
"no 'url' found in 'phone_home' configuration"), name) | |
return | |
url = ph_cfg['url'] | |
token = ph_cfg.get('token', '') | |
post_list = ph_cfg.get('post', 'all') | |
header_dict = ph_cfg.get('headers', {}) | |
tries = ph_cfg.get('tries') | |
try: | |
tries = int(tries) | |
except Exception: | |
tries = 10 | |
util.logexc(log, "Configuration entry 'tries' is not an integer, " | |
"using %s instead", tries) | |
if post_list == "all": | |
post_list = POST_LIST_ALL | |
all_keys = {} | |
all_keys['instance_id'] = cloud.get_instance_id() | |
all_keys['hostname'] = cloud.get_hostname() | |
all_keys['fqdn'] = cloud.get_hostname(fqdn=True) | |
pubkeys = { | |
'pub_key_dsa': '/etc/ssh/ssh_host_dsa_key.pub', | |
'pub_key_rsa': '/etc/ssh/ssh_host_rsa_key.pub', | |
'pub_key_ecdsa': '/etc/ssh/ssh_host_ecdsa_key.pub', | |
'pub_key_ed25519': '/etc/ssh/ssh_host_ed25519_key.pub' | |
} | |
for (name, path) in pubkeys.items(): | |
try: | |
all_keys[name] = util.load_file(path) | |
except Exception: | |
util.logexc(log, "%s: failed to open, can not phone home that " | |
"data!", path) | |
submit_keys = {} | |
for k in post_list: | |
if k in all_keys: | |
submit_keys[k] = all_keys[k] | |
else: | |
submit_keys[k] = None | |
log.warn(("Requested key %s from 'post'" | |
" configuration list not available"), k) | |
# Get them ready to be posted | |
real_submit_keys = {} | |
for (k, v) in submit_keys.items(): | |
if v is None: | |
real_submit_keys[k] = 'N/A' | |
else: | |
real_submit_keys[k] = str(v) | |
# In case the url is parameterized | |
url_params = { | |
'INSTANCE_ID': all_keys['instance_id'], | |
} | |
url = templater.render_string(url, url_params) | |
# Assemble post data payload | |
payload = real_submit_keys.copy() | |
payload.update({ 'token': token }) | |
serv_data = json.dumps({ 'serv_data': payload }) | |
# Finally | |
try: | |
util.read_file_or_url(url, data=serv_data, | |
retries=tries-1, sec_between=3, | |
ssl_details=util.fetch_ssl_details(cloud.paths), | |
headers=header_dict) | |
except Exception: | |
util.logexc(log, "Failed to post phone home data to %s in %s tries", | |
url, tries) | |
# vi: ts=4 expandtab | |
PHPATCH | |
end | |
def phone_home | |
<<~PHONEHOME | |
phone_home: | |
url: "http://123.222.222.222:3000/do_hostkeys/fingerprint" | |
token: #{ServerSession.server_session_token} | |
headers: | |
X-CSRF-Token: #{ServerSession.csrf_token} | |
Content-Type: application/json | |
post: [ pub_key_rsa, instance_id ] | |
tries: 2 | |
PHONEHOME | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment