diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index dd6702382c1d7eb2b15f967251e46859ea3aa040..72cd979f3aec58eed69188761e5c487fc2fad5e0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -43,10 +43,7 @@ variables: ${APT_PROXY:+-e config.apt_proxy=${APT_PROXY}} $CREATE_ENV_VARS $BUILD_DIR - cp -v ${TEST_DIR}/site.yml ${BUILD_DIR} - - sed -i '/ansible_ssh_private_key_file/d' ${BUILD_DIR}/hosts.yml - - sed -i 's/ansible_user:\ vagrant/ansible_user:\ root/' ${BUILD_DIR}/hosts.yml - echo "$(awk '!/- backend/ || ++ctr != 2' ${BUILD_DIR}/hosts.yml)" > ${BUILD_DIR}/hosts.yml - - curl -o float/scripts/floatup.py https://git.autistici.org/ai3/float/-/raw/master/scripts/floatup.py - with-ssh-key ./float/scripts/floatup.py --url $VMINE_URL --ssh $VMINE_SSH --inventory $BUILD_DIR/hosts.yml --ram 3072 --image ${VM_IMAGE:-bullseye} up - sed -i '2 i\ User leap_ci\n IdentityFile /root/.ssh/key' ~/.ssh/config - echo " ProxyJump remotevirt.riseup.net" >> ~/.ssh/config diff --git a/float/.gitlab-ci.yml b/float/.gitlab-ci.yml index 34531018ebf56b03aeccd6f5af4597b35fec07fe..ef06edef9ccc800f2ea4da42929d98061a432e31 100644 --- a/float/.gitlab-ci.yml +++ b/float/.gitlab-ci.yml @@ -25,7 +25,10 @@ variables: ${APT_PROXY:+-e config.apt_proxy=${APT_PROXY}} $CREATE_ENV_VARS $BUILD_DIR - - with-ssh-key floatup ${LIBVIRT:+--ssh $LIBVIRT} --inventory $BUILD_DIR/hosts.yml --ram 2048 --cpu 2 --image ${VM_IMAGE:-bullseye} ${FLOATUP_ARGS} up + - with-ssh-key floatup ${LIBVIRT:+--ssh $LIBVIRT} --inventory $BUILD_DIR/hosts.yml --ram 2048 --cpu 2 --image ${VM_IMAGE:-bookworm} ${FLOATUP_ARGS} up + - ls -al /root/.ssh + - cat /root/.ssh/config + - cat $BUILD_DIR/hosts.yml - with-ssh-key ./test-driver init --no-vagrant $BUILD_DIR - with-ssh-key ./test-driver run $BUILD_DIR after_script: @@ -46,15 +49,15 @@ variables: base_test: <<: *base_test variables: - VM_IMAGE: "bullseye" - CREATE_ENV_VARS: "-e config.float_debian_dist=bullseye -e inventory.group_vars.vagrant.ansible_python_interpreter=/usr/bin/python3" + VM_IMAGE: "bookworm" + CREATE_ENV_VARS: "-e config.float_debian_dist=bookworm" TEST_DIR: "test/base.ref" full_test: <<: *base_test variables: - VM_IMAGE: "bullseye" - CREATE_ENV_VARS: "-e config.float_debian_dist=bullseye -e inventory.group_vars.vagrant.ansible_python_interpreter=/usr/bin/python3" + VM_IMAGE: "bookworm" + CREATE_ENV_VARS: "-e config.float_debian_dist=bookworm" TEST_DIR: "test/full.ref" rules: - if: $CI_MERGE_REQUEST_ID == '' @@ -64,8 +67,8 @@ full_test_review: after_script: - with-ssh-key ./test-driver cleanup --no-vagrant $BUILD_DIR variables: - VM_IMAGE: "bullseye" - CREATE_ENV_VARS: "-e config.float_debian_dist=bullseye -e inventory.group_vars.vagrant.ansible_python_interpreter=/usr/bin/python3" + VM_IMAGE: "bookworm" + CREATE_ENV_VARS: "-e config.float_debian_dist=bookworm -e inventory.group_vars.vagrant.ansible_python_interpreter=/usr/bin/python3" FLOATUP_ARGS: "--state-file .vmine_group_review_$CI_MERGE_REQUEST_ID --ttl 6h --env deploy.env --dashboard-url https://vm.investici.org" TEST_DIR: "test/full.ref" allow_failure: true @@ -103,13 +106,6 @@ stop_full_test_review: # CREATE_ENV_VARS: "--additional-config test/backup.ref/config-backup.yml --playbook test/backup.ref/site.yml" # TEST_DIR: "test/backup.ref" -bookworm_test: - <<: *base_test - variables: - VM_IMAGE: "bookworm" - CREATE_ENV_VARS: "-e config.float_debian_dist=bookworm" - TEST_DIR: "test/full.ref" - docker_build_and_release_tests: stage: docker_build image: quay.io/podman/stable diff --git a/float/.gitrepo b/float/.gitrepo index 60b6a9b1f9554124f75e4e0a3ead379595bede23..90225847b3fb258312fd54870b87c64a26227b24 100644 --- a/float/.gitrepo +++ b/float/.gitrepo @@ -6,7 +6,7 @@ [subrepo] remote = https://git.autistici.org/ai3/float.git branch = master - commit = 89039534fb72c317de51d7a5c2f8e6815d61b982 + commit = 7e37a32b31c5243273ec16e8dfd8b3d48ce663d3 parent = 155d2691324dc97829db4e0a5f77b512bb8c0647 - cmdver = 0.4.7 + cmdver = 0.4.6 method = merge diff --git a/float/docs/old/playbook.md b/float/docs/old/playbook.md deleted file mode 100644 index 3811ec51cfa9f376ce6ffc5eb27dd52b39e175b1..0000000000000000000000000000000000000000 --- a/float/docs/old/playbook.md +++ /dev/null @@ -1,177 +0,0 @@ -Playbook -=== - -This document describes how to perform some common operations in -*float*. - - -## Applying changes - -### Rolling back the configuration - -If you are using a Git repository as your configuration source, -*float* will keep track of which commit has been pushed to production -last, and it will try to prevent you from pushing an old version of -the configuration, failing immediately with an error. This is a simple -check to make sure that people do not inadvertently roll back the -production configuration by pushing from an out-of-date client. - -In most cases what you want to do in that case is to simply run *git -pull* and bring your copy of the repository up to date. But if you -really need to push an old version of the configuration in an -emergency, you can do so by setting the *rollback* value to *true* on -the command-line: - -```shell -$ float run -e rollback=true site.yml -``` - - -## For administrators - -### SSH Client Setup - -If you delegated SSH management to float by setting *enable_ssh* to -true (see the [configuration reference](configuration.md)), float will -create a SSH CA to sign all your host keys. - -You will find the public key for this CA in the -*credentials/ssh/key.pub* file, it will be created the first time you -run the "init-credentials" playbook. - -Assuming that all your target hosts share the same domain (so you can -use a wildcard), you should add the following entry to -*~/.ssh/known_hosts*: - -``` -@cert_authority *.example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAA.... -``` - -Since all logins happen as root, it may be convenient to also add a -section to your *~/.ssh/config* file like the following: - -``` -Host *.example.com - User root -``` - -### Adding an admin account - -Adding a new administrator account is just a matter of editing the -*admins* [configuration variable](configuration.md) and add a new -entry to it. - -The first thing you will need is a hashed version of your -password. The authentication service in float supports a number of -legacy hashing schemes, including those supported by the system -crypt(). The most secure hashing scheme supported is Argon2, and you -can use our custom tool to generate a valid hash. To install it: - -```shell -$ go install git.autistici.org/ai3/go-common/cmd/pwtool -``` - -Run the *pwtool* utility with your new password as an argument, as -shown below: - -```shell -# Do not save your password in the history of your shell -$ export HISTIGNORE="./pwtool.amd64*" -$ ./pwtool.amd64 PASSWORD -``` - -where PASSWORD is your desired password. - -It will output the hashed password. - -Then modify the YAML file *group_vars/all/admins.yml*. At the bare -minimum the new account should have a *name*, *email*, *password* and -*ssh_keys* attributes, e.g.: - -```yaml ---- -admins: - - name: "foo" - email: "foo@example.com" - password: "$a2$3$32768$4$abcdef...." - ssh_keys: - - "ssh-ed25519 AAAAC3Nza..." -``` - -Here above "ssh_keys:" needs to be populated with your public key, -possibly stripped from the trailing user@hostname text (which may leak -your personal information), and "password:" must be the hashed -password you got from *pwtool* earlier. - -### Setting up OTP for an admin account - -First you need to manually generate the OTP secret on your computer: - -```shell -$ SECRET=$(dd if=/dev/urandom bs=20 count=1 2>/dev/null | base32) -$ echo $SECRET -EVUVNACTWRAIERATIZUQA6YQ4WS63RN2 -``` - -Install the package qrencode, and feed the OTP secret to it. -For example with apt ["apt install qrencode" of course]. - -```shell -$ EMAIL="sub@krutt.org" -$ qrencode -t UTF8 "otpauth://totp/example.com:${EMAIL}?secret=${SECRET}&issuer=example.com&algorithm=SHA1&digits=6&period=30" -``` - -and read the qrcode with your favourite app. - -Then add it to your user object in *group_vars/all/admins.yml* as the -*totp_secret* attribute: - -```yaml ---- -admins: - - name: "foo" - totp_secret: "EVUVNACTWRAIERATIZUQA6YQ4WS63RN2" - ... -``` - -Finally, configure your TOTP client (app, YubiKey, etc.) with the same -secret. - -Note that the secret is stored in cleartext in the git repository, so -using a hardware token (U2F) is preferred. - -### Registering a U2F hardware token for an admin account - -In the *group_vars/all/admins.yml* file, you can add the -*u2f_registrations* attribute to accounts, which is a list of the -allowed U2F device registrations. - -To register a new device, you are going to need the *pamu2fcfg* tool -(part of the *pamu2fcfg* Debian package). The following snippet should -produce the two YAML attributes that you need to set: - -```shell -$ pamu2fcfg --nouser --appid https://accounts.example.com \ - | tr -d : \ - | awk -F, '{print "key_handle: \"" $1 "\"\npublic_key: \"" $2 "\""}' -``` - -press enter, touch the key, copy the output and insert it in -*group_vars/all/admins.yml*, the final results should look like: - -```yaml ---- -admins: - - name: "foo" - email: "foo@example.com" - password: "$a2$3$32768$4$abcdef...." - ssh_keys: - - "ssh-ed25519 AAAAC3Nza..." - u2f_registrations: - - key_handle: "r4wWRHgzJjl..." - public_key: "04803e4aff4..." -``` - -**NOTE**: the above will work with *pam_u2f* version 1.0.7, but it will *not* -work with pam_u2f version 1.1.0 due to changes in the output format! - diff --git a/float/docs/reference.md b/float/docs/reference.md index b088a68039b5911cfa9055093dccfd1bee310879..c1e6eb8511ab1af935be4dfebccbd0cee1823f3b 100644 --- a/float/docs/reference.md +++ b/float/docs/reference.md @@ -2798,7 +2798,7 @@ There are some minimal requirements on how your Ansible environment should be set up for this to work: * you must have a *group_vars/all* directory (this is where we'll - write the autogenerated application credentials file *secrets.yml*q) + write the autogenerated application credentials file *secrets.yml*) * you must include float's *playbooks/all.yml* playbook file from the toolkit source directory at the beginning of your playbook * you should use the *float* wrapper instead of running @@ -3241,7 +3241,7 @@ Install the package qrencode, and feed the OTP secret to it. For example with apt ["apt install qrencode" of course]. ```shell -$ EMAIL="sub@krutt.org" +$ EMAIL="foo@example.com" $ qrencode -t UTF8 "otpauth://totp/example.com:${EMAIL}?secret=${SECRET}&issuer=example.com&algorithm=SHA1&digits=6&period=30" ``` @@ -3318,6 +3318,19 @@ If you want more control over this process (Debian upgrades have been event-less for a while now, but it's not always been the case) you can of course run the upgrade manually. +### Decommissioning a host + +When turning down a host, it is necessary, at some point, to +reschedule the services that were there onto some other hosts. To +achieve a smooth transition, this is best done while the host is still +available. + +To do this, set the *turndown* attribute to *true* in the inventory +for the host you want to turn down, and then run *float* once more. +This should safely reschedule all services, and remove them from the +target host. It is then possible to simply shut down the target host +and wipe its data. + # Example scenarios This section will look at some example scenarios and use cases for diff --git a/float/docs/reference.pdf b/float/docs/reference.pdf index c55f860229873a58eaa48b0efa865a48b7a81f3a..7a29260cc0f44d7f4bac04e7b096da388ecd8d67 100644 Binary files a/float/docs/reference.pdf and b/float/docs/reference.pdf differ diff --git a/float/float b/float/float index eae52a4b41c868271de51dee956f69ca1a7ee62a..ce358ce1857283a25f6d7bb4dfcc827a0ac6acd1 100755 --- a/float/float +++ b/float/float @@ -162,13 +162,7 @@ DEFAULT_VARS = { # Ansible inventory (hosts are created dynamically). 'inventory': { 'hosts': {}, - 'group_vars': { - 'vagrant': { - 'ansible_user': 'vagrant', - 'ansible_become': True, - 'ansible_ssh_private_key_file': '~/.vagrant.d/insecure_private_key', - }, - }, + 'group_vars': {}, }, # Ansible configuration. @@ -346,7 +340,7 @@ def _render_skel(target_dir, ctx): def command_create_env(path, services, passwords, playbooks, roles_path, num_hosts, additional_host_groups, additional_configs, ram, domain, infra_domain, - extra_vars): + become, extra_vars): all_vars = DEFAULT_VARS # Set paths in the internal config. @@ -355,6 +349,20 @@ def command_create_env(path, services, passwords, playbooks, all_vars['passwords_yml_path'] = passwords all_vars['playbooks'] = playbooks + # Set connection-related user parameters. + if become == 'root': + all_vars['inventory']['group_vars']['vagrant'] = { + 'ansible_user': 'root', + 'ansible_become': False, + } + else: + all_vars['inventory']['group_vars']['vagrant'] = { + 'ansible_user': become, + 'ansible_become': True, + # For legacy compatibility reasons. + 'ansible_ssh_private_key_file': '~/.vagrant.d/insecure_private_key', + } + # Extend the Ansible roles_path. if roles_path: for rpath in roles_path.split(':'): @@ -548,6 +556,9 @@ memberships, using the --additional-host-group command-line option. create_env_parser.add_argument( '--ram', metavar='MB', type=int, default=3072, help='RAM for each VM when using --vagrant (default: 3072)') + create_env_parser.add_argument( + '--become', metavar='USER', default='root', + help='ansible_user, disable ansible_become if "root"') create_env_parser.add_argument( '--additional-host-group', metavar='GROUP=HOST1[,HOST2...]', dest='additional_host_groups', diff --git a/float/plugins/inventory/float.py b/float/plugins/inventory/float.py index 46c2b25a0e4d0d1c53635da2e197e14f576963c9..e67df79ecb905ae7792d9ae24b952ab3ef688613 100644 --- a/float/plugins/inventory/float.py +++ b/float/plugins/inventory/float.py @@ -282,6 +282,16 @@ def _global_dns_map(inventory): return dns +# Return the hosts that are not available for scheduling, as a +# Python set. +def _unavailable_hosts(inventory): + unavail = set() + for name, values in inventory['hosts'].items(): + if values.get('turndown'): + unavail.add(name) + return unavail + + # Build a group -> hosts map out of an inventory. def _build_group_map(inventory, assignments=None): group_map = {} @@ -499,7 +509,8 @@ class Assignments(object): return str(self._fwd) @classmethod - def _available_hosts(cls, service, group_map, service_hosts_map): + def _available_hosts(cls, service, group_map, service_hosts_map, + unavailable_hosts={}): if 'schedule_with' in service: return service_hosts_map[service['schedule_with']] scheduling_groups = ['all'] @@ -512,7 +523,7 @@ class Assignments(object): if g not in group_map: raise Exception(f'The scheduling_group "{g}" is not defined in inventoy') available_hosts.update(group_map[g]) - return list(available_hosts) + return list(available_hosts.difference(unavailable_hosts)) @classmethod def schedule(cls, services, inventory): @@ -525,6 +536,7 @@ class Assignments(object): """ service_hosts_map = {} service_master_map = {} + unavailable_hosts = _unavailable_hosts(inventory) group_map = _build_group_map(inventory) host_occupation = collections.defaultdict(int) @@ -540,13 +552,16 @@ class Assignments(object): for service_name in sorted(services.keys(), key=_sort_key): service = services[service_name] available_hosts = cls._available_hosts(service, group_map, - service_hosts_map) + service_hosts_map, + unavailable_hosts) num_instances = service.get('num_instances', 'all') if num_instances == 'all': service_hosts = sorted(available_hosts) else: service_hosts = sorted(_binpack( available_hosts, host_occupation, num_instances)) + if not service_hosts: + raise Exception(f'No hosts available to schedule service {service_name}') service_hosts_map[service_name] = service_hosts for h in service_hosts: host_occupation[h] += 1 diff --git a/float/roles/float-base/files/journald.conf b/float/roles/float-base/files/journald.conf index b69850df1d39ae8454044390bc35994ab52518b1..700ceca35bf50b7b7bbfd5a76445bdda020e44bc 100644 --- a/float/roles/float-base/files/journald.conf +++ b/float/roles/float-base/files/journald.conf @@ -1,2 +1,6 @@ [Journal] Storage=volatile +RateLimitIntervalSec=0 +RateLimitBurst=0 +Compress=no +Seal=no diff --git a/float/roles/float-base/tasks/apt.yml b/float/roles/float-base/tasks/apt.yml index 7923add755781d6ddcccab6b8a2e4b3d72fc194e..608ffd89c0dc189ea74dc5ce318108d47debc592 100644 --- a/float/roles/float-base/tasks/apt.yml +++ b/float/roles/float-base/tasks/apt.yml @@ -107,6 +107,7 @@ - acpid - auditd - ca-certificates + - cron - curl - git - gpg diff --git a/float/roles/float-base/tasks/main.yml b/float/roles/float-base/tasks/main.yml index 41b65d59ec0b3780ffdfc762098b1a2c3e103c00..e47d2bb3a5ae986551fe4d0f8df16f661af4c9af 100644 --- a/float/roles/float-base/tasks/main.yml +++ b/float/roles/float-base/tasks/main.yml @@ -40,14 +40,6 @@ - include_tasks: rollback_protection.yml when: "git_revision != 'none' and not testing|default(True)" -# Detect virtual machines / physical hardware. -- name: Detect virtual machine - slurp: - src: "/sys/class/dmi/id/sys_vendor" - register: slurp_sysfs_dmi_vendor -- set_fact: - float_is_vm: "{{ slurp_sysfs_dmi_vendor['content'] | b64decode == 'QEMU' }}" - # Create the /usr/lib/float and /var/lib/float directories for # internal scripts. - file: diff --git a/float/roles/float-infra-nginx/defaults/main.yml b/float/roles/float-infra-nginx/defaults/main.yml index 932c06c8eec426906fef261333356569226837ea..cba99a727bf4dbf753214980633d0195513a4e19 100644 --- a/float/roles/float-infra-nginx/defaults/main.yml +++ b/float/roles/float-infra-nginx/defaults/main.yml @@ -22,7 +22,7 @@ nginx_limit_perserver_rate: "100r/s" nginx_limit_perserver_burst: 100 # Various top-level NGINX configuration options that might need tuning. -nginx_worker_connections: 4096 +nginx_worker_connections: 65536 nginx_keepalive_timeout: "20s" nginx_server_names_hash_max_size: 2048 nginx_server_names_hash_bucket_size: 2048 @@ -36,3 +36,4 @@ nginx_custom_error_pages: true # nginx_install_custom_error_pages: install float's custom error pages # in /var/www/html/__errors/. nginx_install_custom_error_pages: true + diff --git a/float/roles/float-infra-nginx/tasks/nginx.yml b/float/roles/float-infra-nginx/tasks/nginx.yml index e30b833a840d06d2f5f170093d2fec6723c69876..7d0c0722dfdf2a151ae19fa23bece9c29719c8af 100644 --- a/float/roles/float-infra-nginx/tasks/nginx.yml +++ b/float/roles/float-infra-nginx/tasks/nginx.yml @@ -67,8 +67,8 @@ - proxy - name: Install NGINX systemd unit - copy: - src: nginx.service + template: + src: nginx.service.j2 dest: /etc/systemd/system/nginx.service notify: reload nginx diff --git a/float/roles/float-infra-nginx/templates/config/conf.d/limits.conf b/float/roles/float-infra-nginx/templates/config/conf.d/limits.conf index 380d4887a488bcb4b945d288c33324ace0a990da..7c8fb60f06a201c56f4ce48fdc0c09ddac9aa01b 100644 --- a/float/roles/float-infra-nginx/templates/config/conf.d/limits.conf +++ b/float/roles/float-infra-nginx/templates/config/conf.d/limits.conf @@ -1,5 +1,6 @@ # Per-IP rate limiting. -limit_req_zone $binary_remote_addr zone=perip:32m rate={{ nginx_limit_perip_rate }}; +# A 128M zone holds information on about 1M IPs. +limit_req_zone $binary_remote_addr zone=perip:128m rate={{ nginx_limit_perip_rate }}; # Per-server rate limiting. limit_req_zone $server_name zone=perserver:10m rate={{ nginx_limit_perserver_rate }}; diff --git a/float/roles/float-infra-nginx/templates/config/conf.d/proxy.conf b/float/roles/float-infra-nginx/templates/config/conf.d/proxy.conf index 7d31efc24a25d7ba30aca733b6d9e0d4c6becbbc..c67e0401b5c7145dcfec69b00788b0580a24e964 100644 --- a/float/roles/float-infra-nginx/templates/config/conf.d/proxy.conf +++ b/float/roles/float-infra-nginx/templates/config/conf.d/proxy.conf @@ -32,3 +32,9 @@ proxy_cache_min_uses 2; # Show our own error pages, not the remote ones. proxy_intercept_errors on; +# Ensure we only send one upstream request, and allow NGINX to serve +# stale data while updating. +proxy_cache_use_stale updating; +proxy_cache_background_update on; +proxy_cache_lock on; + diff --git a/float/roles/float-infra-nginx/files/nginx.service b/float/roles/float-infra-nginx/templates/nginx.service.j2 similarity index 94% rename from float/roles/float-infra-nginx/files/nginx.service rename to float/roles/float-infra-nginx/templates/nginx.service.j2 index dda45307e70a7183df007d72151f3bea742c0f76..ad0a7fcc3d104ba638ac67308206249fa527fbfe 100644 --- a/float/roles/float-infra-nginx/files/nginx.service +++ b/float/roles/float-infra-nginx/templates/nginx.service.j2 @@ -18,7 +18,7 @@ ExecStop=/bin/kill -TERM $MAINPID User=nginx Group=nginx -LimitNOFILE=65535 +LimitNOFILE={{ nginx_worker_connections * 2 }} NoNewPrivileges=yes PrivateTmp=yes @@ -36,5 +36,7 @@ AmbientCapabilities=CAP_NET_BIND_SERVICE RuntimeDirectory=nginx RuntimeDirectoryMode=750 +JournalNamespace=nginx + [Install] WantedBy=multi-user.target diff --git a/float/roles/float-infra-prometheus/templates/rules/rules_nginx.conf.yml b/float/roles/float-infra-prometheus/templates/rules/rules_nginx.conf.yml index 60b59bb0e4fd51a18bc2026dab5f1228b6c0ee53..d9abacdf866f3f3da931d8953476a75fbe8db82f 100644 --- a/float/roles/float-infra-prometheus/templates/rules/rules_nginx.conf.yml +++ b/float/roles/float-infra-prometheus/templates/rules/rules_nginx.conf.yml @@ -17,6 +17,8 @@ groups: expr: (global:nginx_http_requests_errs:rate5m / global:nginx_http_requests_total:rate5m) - record: global:nginx_http_cached_requests:ratio expr: clamp_max(sum(rate(nginx_http_requests_cache[5m])) by (vhost, cache_status) / ignoring (cache_status) group_left global:nginx_http_requests_200:rate5m, 1) + - record: host:nginx_http_cached_requests:ratio + expr: clamp_max(sum(rate(nginx_http_requests_cache[5m])) by (host, cache_status) / ignoring (cache_status) group_left sum(rate(nginx_http_requests{code="200"}[5m])) by (host), 1) - name: http_requests_ms_histogram rules: diff --git a/float/scripts/floatup.py b/float/scripts/floatup.py index 5647d7dc306e28791a4f8fb2e4536d6000b68ad3..fa3aa850d2121401a6a99d07eb6613a7211ce0eb 100755 --- a/float/scripts/floatup.py +++ b/float/scripts/floatup.py @@ -15,37 +15,6 @@ import yaml import zlib -# The Vagrant "insecure" SSH key that is used to log onto the VMs. -INSECURE_PRIVATE_KEY = '''-----BEGIN RSA PRIVATE KEY----- -MIIEogIBAAKCAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzI -w+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoP -kcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2 -hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NO -Td0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcW -yLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQIBIwKCAQEA4iqWPJXtzZA68mKd -ELs4jJsdyky+ewdZeNds5tjcnHU5zUYE25K+ffJED9qUWICcLZDc81TGWjHyAqD1 -Bw7XpgUwFgeUJwUlzQurAv+/ySnxiwuaGJfhFM1CaQHzfXphgVml+fZUvnJUTvzf -TK2Lg6EdbUE9TarUlBf/xPfuEhMSlIE5keb/Zz3/LUlRg8yDqz5w+QWVJ4utnKnK -iqwZN0mwpwU7YSyJhlT4YV1F3n4YjLswM5wJs2oqm0jssQu/BT0tyEXNDYBLEF4A -sClaWuSJ2kjq7KhrrYXzagqhnSei9ODYFShJu8UWVec3Ihb5ZXlzO6vdNQ1J9Xsf -4m+2ywKBgQD6qFxx/Rv9CNN96l/4rb14HKirC2o/orApiHmHDsURs5rUKDx0f9iP -cXN7S1uePXuJRK/5hsubaOCx3Owd2u9gD6Oq0CsMkE4CUSiJcYrMANtx54cGH7Rk -EjFZxK8xAv1ldELEyxrFqkbE4BKd8QOt414qjvTGyAK+OLD3M2QdCQKBgQDtx8pN -CAxR7yhHbIWT1AH66+XWN8bXq7l3RO/ukeaci98JfkbkxURZhtxV/HHuvUhnPLdX -3TwygPBYZFNo4pzVEhzWoTtnEtrFueKxyc3+LjZpuo+mBlQ6ORtfgkr9gBVphXZG -YEzkCD3lVdl8L4cw9BVpKrJCs1c5taGjDgdInQKBgHm/fVvv96bJxc9x1tffXAcj -3OVdUN0UgXNCSaf/3A/phbeBQe9xS+3mpc4r6qvx+iy69mNBeNZ0xOitIjpjBo2+ -dBEjSBwLk5q5tJqHmy/jKMJL4n9ROlx93XS+njxgibTvU6Fp9w+NOFD/HvxB3Tcz -6+jJF85D5BNAG3DBMKBjAoGBAOAxZvgsKN+JuENXsST7F89Tck2iTcQIT8g5rwWC -P9Vt74yboe2kDT531w8+egz7nAmRBKNM751U/95P9t88EDacDI/Z2OwnuFQHCPDF -llYOUI+SpLJ6/vURRbHSnnn8a/XG+nzedGH5JGqEJNQsz+xT2axM0/W/CRknmGaJ -kda/AoGANWrLCz708y7VYgAtW2Uf1DPOIYMdvo6fxIB5i9ZfISgcJ/bbCUkFrhoH -+vq/5CIWxCPp0f85R4qxxQ5ihxJ0YDQT9Jpx4TMss4PSavPaBH3RXow5Ohe+bYoQ -NE5OgEXk2wVfZczCZpigBKbKZHNYcelXtTt/nP3rsCuGcM4h53s= ------END RSA PRIVATE KEY----- -''' - - def parse_inventory(path, host_attrs): with open(path) as fd: inventory = yaml.safe_load(fd) @@ -87,16 +56,24 @@ def encode_dashboard_request(req): return base64.urlsafe_b64encode(comp.flush()).decode('ascii') -def install_vagrant_ssh_key(): - # Install the SSH key as Vagrant would do, for compatibility. - key_path = os.path.join( - os.getenv('HOME'), '.vagrant.d', 'insecure_private_key') - if os.path.exists(key_path): - return - os.makedirs(os.path.dirname(key_path), mode=0o700, exist_ok=True) - with open(key_path, 'w') as fd: - fd.write(INSECURE_PRIVATE_KEY) - os.chmod(key_path, 0o600) +def generate_ssh_key(): + path = '/root/.ssh/temp' + if os.getenv('HOME'): + path = os.getenv('HOME') + '/.ssh/temp' + os.makedirs(os.path.dirname(path), mode=0o700, exist_ok=True) + subprocess.check_call(['ssh-keygen', '-t', 'ed25519', '-f', path, '-C', '', '-N', '']) + return path + + +def generate_ssh_config(inventory, private_key_path): + netglob = re.sub(r'\.0/24$', '.*', inventory['network']) + return f''' +Host {netglob} + User root + IdentityFile {private_key_path} + StrictHostKeyChecking no + UserKnownHostsFile /dev/null +''' def main(): @@ -135,8 +112,11 @@ def main(): help='vmine dashboard base URL (for Gitlab CI)') parser.add_argument( '--ssh-key', metavar='FILE', - type=argparse.FileType('r'), help='root SSH key to install on VMs') + parser.add_argument( + '--ssh-config', metavar='FILE', + default='/root/.ssh/config', + help='append SSH config to this file') parser.add_argument( '--name', metavar='NAME', help='group name (for named groups)') @@ -153,14 +133,19 @@ def main(): host_attrs['cpu'] = args.cpu if args.image: host_attrs['image'] = args.image + req = parse_inventory(args.inventory, host_attrs) req['ttl'] = args.ttl if args.name: req['name'] = args.name if args.ssh_key: - req['ssh_key'] = args.ssh_key + ssh_key_path = args.ssh_key else: - install_vagrant_ssh_key() + ssh_key_path = generate_ssh_key() + with open(ssh_key_path + '.pub', 'r') as fd: + req['ssh_key'] = fd.read().strip() + + os.umask(0o077) print(f'creating VM group with attrs {host_attrs} ...') print(f'vmine request: {req}') @@ -170,13 +155,20 @@ def main(): fd.write(group_id) print(f'created VM group {group_id}') + if args.ssh_config: + print(f'updating ssh config') + with open(args.ssh_config, 'a') as fd: + fd.write(generate_ssh_config(req, ssh_key_path)) + if args.env: with open(args.env, 'w') as fd: fd.write(f'VMINE_ID={group_id}\n') if args.dashboard_url: base_url = args.dashboard_url.rstrip('/') payload = encode_dashboard_request(req) - fd.write(f'VMINE_GROUP_URL={base_url}/dash/{payload}\n') + dashboard_url = f'{base_url}/dash/{payload}' + fd.write(f'VMINE_GROUP_URL={dashboard_url}\n') + print(f'dashboard URL: {dashboard_url}') elif args.cmd == 'down': req = {} @@ -192,8 +184,11 @@ def main(): return req['group_id'] = group_id print(f'stopping VM group {group_id}...') - do_request(args.url + '/api/stop-group', args.ssh, req) - if args.state_file: + try: + do_request(args.url + '/api/stop-group', args.ssh, req) + except: + pass + if args.state_file and os.path.exists(args.state_file): os.remove(args.state_file) diff --git a/float/test/integration-test.yml b/float/test/integration-test.yml index 449b919e26415a4c7a065831270f009f0e4bd352..e43a784e9e45d3cc36ce72bade708f98e8ef9f94 100644 --- a/float/test/integration-test.yml +++ b/float/test/integration-test.yml @@ -14,7 +14,7 @@ failed_when: "test_container_image.rc not in [0, 42]" - name: Run tests - command: "docker run --rm --network host --mount type=bind,source=/tmp/test-config.yml,destination=/test-config.yml {{ test_image }}" + command: "podman run --rm --network host --mount type=bind,source=/tmp/test-config.yml,destination=/test-config.yml {{ test_image }}" vars: test_image: "registry.git.autistici.org/ai3/float:integration-test"