@cryptotaxi247 / infra-1 / commits / fe574006

hydra packet importer: load keys from metadata, not ssh-keyscan

Graham Christensen committed Oct 6, 2019 at 21:40 UTC fe57400644731acc418701b58da9cb774f5ab815
2 files changed +57 -16
hydra-packet-importer/README.md new
+27
@@ -0,0 +1,27 @@
1 +Imports builders' and their SSH keys from the Packet API. Requires the
2 +builder run this script at startup:
3 +
4 +```bash
5 +#!/usr/bin/env nix-shell
6 +#!nix-shell -i bash -p curl jq
7 +
8 +set -eux
9 +
10 +root_url=$(curl https://metadata.packet.net/metadata | jq -r .phone_home_url | rev | cut -d '/' -f2- | rev)
11 +url="$root_url/events"
12 +
13 +
14 +tell() {
15 + data=$(
16 + echo "{}" \
17 + | jq '.state = $state | .code = ($code | tonumber) | .message = $message' \
18 + --arg state "$1" \
19 + --arg code "$2" \
20 + --arg message "$3"
21 + )
22 +
23 + curl -v -X POST -d "$data" "$url"
24 +}
25 +
26 +tell succeeded 1001 "$(cat /etc/ssh/ssh_host_ed25519_key.pub)"
27 +```
hydra-packet-importer/import.py
+30 -16
@@ -33,14 +33,42 @@ def get_devices(manager):
33 if not set(device['tags']).isdisjoint(config['skip_tags']):
34 continue
35
36 + host_key = get_device_key(manager, device)
37 + if host_key is None:
38 + continue
39 +
40 devices.append({
41 "hostname": device['hostname'],
42 "address": "{}.packethost.net".format(device['short_id']),
39 - "type": device['plan']['name']
43 + "type": device['plan']['name'],
44 + "host_key": host_key,
45 })
46
47 return devices
48
49 +def get_device_key(manager, device):
50 + # ... 50 is probably enough.
51 + events_url = 'devices/{}/events?per_page=50'.format(device['id'])
52 + debug(events_url)
53 + data = manager.call_api(events_url)
54 +
55 + ssh_key = None
56 + for event in data['events']:
57 + if event['type'] == 'provisioning.104.01':
58 + # we reached a "Device connected to DHCP system" event,
59 + # indicating a reboot.
60 + #
61 + # The most first SSH key after DHCP is the one we want,
62 + # in case someone sends a bogus SSH key to the metadata
63 + # API after the post-boot hook.
64 + #
65 + # If we receive a LOT of spam (> 50 spams!) like that, we
66 + # will return None because we never reach this message.
67 + return ssh_key
68 + if event['type'] == 'user.1001':
69 + ssh_key = event['body']
70 + return None
71 +
72 def main(config):
73 rows = []
74 manager = packet.Manager(auth_token=config['token'])
@@ -64,20 +92,6 @@ def main(config):
92 lookup = lambda key: specific_stats.get(key, device.get(key, default_stats.get(key)))
93 lookup_default = lambda key, default: default if not lookup(key) else lookup(key)
94
67 - r = subprocess.check_output([
68 - "ssh-keyscan",
69 - "-4", # force IPv4
70 - "-T", "5", # Timeout 5 seconds
71 - "-t", "ed25519", # Only ed25519 keys
72 - lookup("address")
73 - ]).decode("utf-8")
74 -
75 - elems = r.split(" ", 1)
76 - if len(elems) != 2:
77 - debug("# Skipped due keyscan failed to split on ' '")
78 - continue
79 - key = elems[1]
80 -
95 # root@address system,list /var/lib/ssh.key maxJobs speedFactor feature,list mandatory,features public-host-key
96 rows.append(" ".join([
97 "{user}@{host}".format(user=lookup("user"),host=lookup("address")),
@@ -87,7 +101,7 @@ def main(config):
101 str(lookup("speed_factor")),
102 ",".join(lookup_default("features", ["-"])),
103 ",".join(lookup_default("mandatory_features", ["-"])),
90 - base64.b64encode(key.encode()).decode("utf-8")
104 + base64.b64encode(device['host_key'].encode()).decode("utf-8")
105 ]))
106
107 debug("# {} / {}".format(len(rows),found))