diff --git a/ic-os/components/misc/config/setupos/config.sh b/ic-os/components/misc/config/setupos/config.sh new file mode 100644 index 00000000000..a16d9fcceba --- /dev/null +++ b/ic-os/components/misc/config/setupos/config.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# Shared config utilities. + +# Retrieves a value from the config.json file using a JSON path. +# Arguments: +# $1 - JSON path to the desired value (e.g., '.icos_settings.nns_urls') +# Note: If the key is not found, this function will return null. +function get_config_value() { + local CONFIG_FILE="/var/ic/config/config.json" + local key=$1 + jq -r "${key}" "${CONFIG_FILE}" +} diff --git a/ic-os/components/setupos-scripts/check-config.sh b/ic-os/components/setupos-scripts/check-config.sh new file mode 100644 index 00000000000..06a61de1ce5 --- /dev/null +++ b/ic-os/components/setupos-scripts/check-config.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash + +# check-config.sh verifies the existence of the configuration JSON file created by config.service, +# halting the installation if not found. + +set -o nounset +set -o pipefail + +SHELL="/bin/bash" +PATH="/sbin:/bin:/usr/sbin:/usr/bin" + +source /opt/ic/bin/functions.sh + +check_config_file() { + echo "* Checking Config..." + local CONFIG_FILE="/var/ic/config/config.json" + + if [ -f "${CONFIG_FILE}" ]; then + local config_contents=$(cat "${CONFIG_FILE}") + echo -e "Configuration file '${CONFIG_FILE}' exists.\n" + echo -e "File contents:\n${config_contents}" + else + local service_logs=$(journalctl -u config.service --no-pager) + local log_message="Error creating SetupOS configuration. Configuration file '${CONFIG_FILE}' does not exist.\n\nConfig.service logs:\n${service_logs}" + + log_and_halt_installation_on_error 1 "${log_message}" + fi +} + +# Establish run order +main() { + log_start "$(basename $0)" + check_config_file + log_end "$(basename $0)" +} + +main diff --git a/ic-os/components/setupos-scripts/check-hardware.sh b/ic-os/components/setupos-scripts/check-hardware.sh index 8bfd16a30e6..67261c63216 100644 --- a/ic-os/components/setupos-scripts/check-hardware.sh +++ b/ic-os/components/setupos-scripts/check-hardware.sh @@ -32,8 +32,6 @@ GEN2_MINIMUM_AGGREGATE_DISK_SIZE=32000000000000 GEN1_MINIMUM_DISK_SIZE=3200000000000 GEN1_MINIMUM_AGGREGATE_DISK_SIZE=32000000000000 -CONFIG_DIR="/var/ic/config" - function check_generation() { echo "* Checking Generation..." @@ -249,6 +247,7 @@ function verify_disks() { function verify_deployment_path() { echo "* Verifying deployment path..." + if [[ ${GENERATION} == 2 ]] && [[ ! -f "${CONFIG_DIR}/node_operator_private_key.pem" ]]; then echo -e "\n\n\n\n\n\n" echo -e "\033[1;31mWARNING: Gen2 hardware detected but no Node Operator Private Key found.\033[0m" @@ -261,33 +260,6 @@ function verify_deployment_path() { fi } -# TODO(NODE-1477): delete in configuration revamp integration -CONFIG="${CONFIG:=/var/ic/config/config.ini}" - -function read_variables() { - # Read limited set of keys. Be extra-careful quoting values as it could - # otherwise lead to executing arbitrary shell code! - while IFS="=" read -r key value; do - case "$key" in - "node_reward_type") node_reward_type="${value}" ;; - esac - done <"${CONFIG}" -} - -function validate_node_reward() { - read_variables - if [[ -z "$node_reward_type" ]]; then - echo "Node reward type is not set. Skipping validation." - return 0 - fi - - if [[ ! "$node_reward_type" =~ ^type[0-9]+(\.[0-9])?$ ]]; then - log_and_halt_installation_on_error 1 "Configuration error: node_reward_type is invalid: ${node_reward_type}" - fi - - echo "Valid node reward type: ${node_reward_type}" -} - # Establish run order main() { log_start "$(basename $0)" @@ -297,7 +269,6 @@ main() { verify_memory verify_disks verify_deployment_path - validate_node_reward else echo "* Hardware checks skipped by request via kernel command line" GENERATION=2 diff --git a/ic-os/components/setupos-scripts/check-network.sh b/ic-os/components/setupos-scripts/check-network.sh index 094cb5e6db4..0f06429318b 100755 --- a/ic-os/components/setupos-scripts/check-network.sh +++ b/ic-os/components/setupos-scripts/check-network.sh @@ -6,24 +6,16 @@ set -o pipefail SHELL="/bin/bash" PATH="/sbin:/bin:/usr/sbin:/usr/bin" +source /opt/ic/bin/config.sh source /opt/ic/bin/functions.sh -CONFIG="${CONFIG:=/var/ic/config/config.ini}" -DEPLOYMENT="${DEPLOYMENT:=/data/deployment.json}" - -function read_variables() { - # Read limited set of keys. Be extra-careful quoting values as it could - # otherwise lead to executing arbitrary shell code! - while IFS="=" read -r key value; do - case "$key" in - "ipv6_prefix") ipv6_prefix="${value}" ;; - "ipv6_gateway") ipv6_gateway="${value}" ;; - "ipv4_address") ipv4_address="${value}" ;; - "ipv4_prefix_length") ipv4_prefix_length="${value}" ;; - "ipv4_gateway") ipv4_gateway="${value}" ;; - "domain") domain="${value}" ;; - esac - done <"${CONFIG}" +function read_config_variables() { + ipv6_prefix=$(get_config_value '.network_settings.ipv6_config.Deterministic.prefix') + ipv6_gateway=$(get_config_value '.network_settings.ipv6_config.Deterministic.gateway') + ipv4_address=$(get_config_value '.network_settings.ipv4_config.address') + ipv4_prefix_length=$(get_config_value '.network_settings.ipv4_config.prefix_length') + ipv4_gateway=$(get_config_value '.network_settings.ipv4_config.gateway') + domain_name=$(get_config_value '.network_settings.domain_name') } # WARNING: Uses 'eval' for command execution. @@ -109,11 +101,13 @@ function print_network_settings() { echo "* Printing user defined network settings..." echo " IPv6 Prefix : ${ipv6_prefix}" echo " IPv6 Gateway: ${ipv6_gateway}" - if [[ -v ipv4_address && -n ${ipv4_address} && -v ipv4_prefix_length && -n ${ipv4_prefix_length} && -v ipv4_gateway && -n ${ipv4_gateway} && -v domain && -n ${domain} ]]; then + if [[ -n ${ipv4_address} && -n ${ipv4_prefix_length} && -n ${ipv4_gateway} ]]; then echo " IPv4 Address: ${ipv4_address}" echo " IPv4 Prefix Length: ${ipv4_prefix_length}" echo " IPv4 Gateway: ${ipv4_gateway}" - echo " Domain name : ${domain}" + fi + if [[ -n ${domain_name} ]]; then + echo " Domain name : ${domain_name}" fi echo " " @@ -134,10 +128,10 @@ function validate_domain_name() { local domain_part local -a domain_parts - IFS='.' read -ra domain_parts <<<"${domain}" + IFS='.' read -ra domain_parts <<<"${domain_name}" if [ ${#domain_parts[@]} -lt 2 ]; then - log_and_halt_installation_on_error 1 "Domain validation error: less than two domain parts in domain: ${domain}" + log_and_halt_installation_on_error 1 "Domain validation error: less than two domain parts in domain: ${domain_name}" fi for domain_part in "${domain_parts[@]}"; do @@ -184,17 +178,13 @@ function ping_ipv6_gateway() { echo " " } -function assemble_nns_nodes_list() { - NNS_URL_STRING=$(/opt/ic/bin/fetch-property.sh --key=.nns.url --config=${DEPLOYMENT}) - IFS=',' read -r -a NNS_URL_LIST <<<"$NNS_URL_STRING" -} - function query_nns_nodes() { echo "* Querying NNS nodes..." + local nns_url_list=($(get_config_value '.icos_settings.nns_urls' | jq -r '.[]')) local success=false - # At least one of the provided URLs needs to work. - for url in "${NNS_URL_LIST[@]}"; do + + for url in "${nns_url_list[@]}"; do # When running against testnets, we need to ignore self signed certs # with `--insecure`. This check is only meant to confirm from SetupOS # that NNS urls are reachable, so we do not mind that it is "weak". @@ -218,7 +208,7 @@ function query_nns_nodes() { main() { log_start "$(basename $0)" if kernel_cmdline_bool_default_true ic.setupos.check_network; then - read_variables + read_config_variables get_network_settings print_network_settings @@ -229,7 +219,6 @@ main() { fi ping_ipv6_gateway - assemble_nns_nodes_list query_nns_nodes else echo "* Network checks skipped by request via kernel command line" diff --git a/ic-os/components/setupos-scripts/config.service b/ic-os/components/setupos-scripts/config.service index 2db6cdf7dc0..f35bbae5eb3 100644 --- a/ic-os/components/setupos-scripts/config.service +++ b/ic-os/components/setupos-scripts/config.service @@ -6,9 +6,9 @@ Before=setupos.service [Service] Type=oneshot RemainAfterExit=true -ExecStart=/opt/ic/bin/output-wrapper.sh /dev/ttyS0 /opt/ic/bin/config.sh -StandardOutput=tty -StandardError=tty +ExecStart=/opt/ic/bin/config create-setupos-config +StandardOutput=journal+console +StandardError=journal+console [Install] WantedBy=multi-user.target diff --git a/ic-os/components/setupos-scripts/config.sh b/ic-os/components/setupos-scripts/config.sh deleted file mode 100755 index d4219d95637..00000000000 --- a/ic-os/components/setupos-scripts/config.sh +++ /dev/null @@ -1,121 +0,0 @@ -#!/usr/bin/env bash - -set -o nounset -set -o pipefail - -SHELL="/bin/bash" -PATH="/sbin:/bin:/usr/sbin:/usr/bin" - -CONFIG_DIR="/config" -CONFIG_TMP="/var/ic/config" -CONFIG_INI="${CONFIG_DIR}/config.ini" -CONFIG_INI_CLONE="${CONFIG_TMP}/config.ini" -SSH_AUTHORIZED_KEYS="${CONFIG_DIR}/ssh_authorized_keys" -SSH_AUTHORIZED_KEYS_CLONE="${CONFIG_TMP}/ssh_authorized_keys" - -source /opt/ic/bin/functions.sh - -# Define empty variables so they are not unset -ipv6_prefix="" -ipv6_gateway="" - -function print_config_file() { - if [ -e "${CONFIG_INI}" ]; then - echo "Found ${CONFIG_INI}. Contents:" - cat "${CONFIG_INI}" - else - log_and_halt_installation_on_error "1" "config.ini not found. Please copy a valid config.ini to the SetupOS installer config partition." - fi - -} - -function create_config_tmp() { - if [ ! -e "${CONFIG_TMP}" ]; then - # Create fresh config tmp directory - mkdir -p "${CONFIG_TMP}" - log_and_halt_installation_on_error "${?}" "Unable to create new '${CONFIG_TMP}' directory." - fi -} - -function clone_config() { - cp "${CONFIG_INI}" "${CONFIG_INI_CLONE}" - log_and_halt_installation_on_error "${?}" "Unable to copy 'config.ini' configuration file." - - if [ ! -f "${CONFIG_INI_CLONE}" ]; then - log_and_halt_installation_on_error "1" "Cloned 'config.ini' configuration file does not exist." - fi - - if [ -f "${CONFIG_DIR}/node_operator_private_key.pem" ]; then - cp ${CONFIG_DIR}/node_operator_private_key.pem ${CONFIG_TMP}/node_operator_private_key.pem - log_and_halt_installation_on_error "${?}" "Unable to copy 'node_operator_private_key.pem' configuration file." - fi - - if [ -d "${SSH_AUTHORIZED_KEYS}" ]; then - cp -r "${SSH_AUTHORIZED_KEYS}" "${CONFIG_TMP}" - log_and_halt_installation_on_error "${?}" "Unable to copy 'ssh_authorized_keys' directory." - else - log_and_halt_installation_on_error "1" "Unable to read 'ssh_authorized_keys' directory." - fi - - if [ ! -d "${SSH_AUTHORIZED_KEYS_CLONE}" ]; then - log_and_halt_installation_on_error "1" "Cloned 'ssh_authorized_keys' directory does not exist." - fi -} - -function normalize_config() { - CONFIG_VAR=$(cat "${CONFIG_INI_CLONE}" | tr '\r' '\n') - echo "${CONFIG_VAR}" >"${CONFIG_INI_CLONE}" - - sed -i 's/#.*$//g' "${CONFIG_INI_CLONE}" - log_and_halt_installation_on_error "${?}" "Unable to remove comments from 'config.ini'." - - sed -i 's/"//g' "${CONFIG_INI_CLONE}" - log_and_halt_installation_on_error "${?}" "Unable to replace double-quote characters in 'config.ini'." - - sed -i "s/'//g" "${CONFIG_INI_CLONE}" - log_and_halt_installation_on_error "${?}" "Unable to replace single-quote characters in 'config.ini'." - - sed -i 's/.*/\L&/' "${CONFIG_INI_CLONE}" - log_and_halt_installation_on_error "${?}" "Unable to convert upper- to lower-case in 'config.ini'." - - sed -i '/^$/d' "${CONFIG_INI_CLONE}" - log_and_halt_installation_on_error "${?}" "Unable to remove empty lines in 'config.ini'." - - echo -e '\n' >>"${CONFIG_INI_CLONE}" - log_and_halt_installation_on_error "${?}" "Unable to inject extra new-line at the end of 'config.ini'." -} - -function read_variables() { - # Read limited set of keys. Be extra-careful quoting values as it could - # otherwise lead to executing arbitrary shell code! - while IFS="=" read -r key value; do - case "$key" in - "ipv6_prefix") ipv6_prefix="${value}" ;; - "ipv6_gateway") ipv6_gateway="${value}" ;; - esac - done <"${CONFIG_INI_CLONE}" -} - -function verify_variables() { - if [ -z "${ipv6_prefix}" ]; then - log_and_halt_installation_on_error "1" "Variable 'ipv6_prefix' is not defined in 'config.ini'." - fi - - if [ -z "${ipv6_gateway}" ]; then - log_and_halt_installation_on_error "1" "Variable 'ipv6_gateway' is not defined in 'config.ini'." - fi -} - -# Establish run order -main() { - log_start "$(basename $0)" - print_config_file - create_config_tmp - clone_config - normalize_config - read_variables - verify_variables - log_end "$(basename $0)" -} - -main diff --git a/ic-os/components/setupos-scripts/setup-hostos-config.sh b/ic-os/components/setupos-scripts/setup-hostos-config.sh index 0893a0c69aa..113e635e6cf 100755 --- a/ic-os/components/setupos-scripts/setup-hostos-config.sh +++ b/ic-os/components/setupos-scripts/setup-hostos-config.sh @@ -5,8 +5,9 @@ set -o pipefail SHELL="/bin/bash" PATH="/sbin:/bin:/usr/sbin:/usr/bin" -CONFIG_DIR="/var/ic/config" +CONFIG_DIR="/config" +source /opt/ic/bin/config.sh source /opt/ic/bin/functions.sh function mount_config_partition() { @@ -20,6 +21,7 @@ function mount_config_partition() { } function copy_config_files() { + # TODO(NODE-1518): delete config.ini copying after switch to new icos config echo "* Copying 'config.ini' to hostOS config partition..." if [ -f "${CONFIG_DIR}/config.ini" ]; then cp ${CONFIG_DIR}/config.ini /media/ @@ -28,29 +30,61 @@ function copy_config_files() { log_and_halt_installation_on_error "1" "Configuration file 'config.ini' does not exist." fi + # TODO(NODE-1518): delete deployment.json copying after switch to new icos config + echo "* Copying deployment.json to config partition..." + cp /data/deployment.json /media/ + log_and_halt_installation_on_error "${?}" "Unable to copy deployment.json to hostOS config partition." + echo "* Copying SSH authorized keys..." - if [ -d "${CONFIG_DIR}/ssh_authorized_keys" ]; then - cp -r ${CONFIG_DIR}/ssh_authorized_keys /media/ - log_and_halt_installation_on_error "${?}" "Unable to copy SSH authorized keys to hostOS config partition." + use_ssh_authorized_keys=$(get_config_value '.icos_settings.use_ssh_authorized_keys') + if [[ "${use_ssh_authorized_keys,,}" == "true" ]]; then + if [ -d "${CONFIG_DIR}/ssh_authorized_keys" ]; then + cp -a "${CONFIG_DIR}/ssh_authorized_keys" /media/ + log_and_halt_installation_on_error "${?}" "Unable to copy SSH authorized keys to hostOS config partition." + else + log_and_halt_installation_on_error "1" "use_ssh_authorized_keys set to true but not found" + fi else - log_and_halt_installation_on_error "1" "Directory 'ssh_authorized_keys' does not exist." + echo >&2 "SSH keys not in use." fi echo "* Copying node operator private key..." - if [ -f "${CONFIG_DIR}/node_operator_private_key.pem" ]; then - cp ${CONFIG_DIR}/node_operator_private_key.pem /media/ - log_and_halt_installation_on_error "${?}" "Unable to copy node operator private key to hostOS config partition." + use_node_operator_private_key=$(get_config_value '.icos_settings.use_node_operator_private_key') + if [[ "${use_node_operator_private_key,,}" == "true" ]]; then + if [ -f "${CONFIG_DIR}/node_operator_private_key.pem" ]; then + cp "${CONFIG_DIR}/node_operator_private_key.pem" /media/ + log_and_halt_installation_on_error "${?}" "Unable to copy node operator private key to hostOS config partition." + else + log_and_halt_installation_on_error "1" "use_node_operator_private_key set to true but not found" + fi else - echo "node_operator_private_key.pem does not exist, requiring HSM." + echo >&2 "Warning: node_operator_private_key.pem does not exist, requiring HSM." fi - echo "* Copying deployment.json to config partition..." - cp /data/deployment.json /media/ - log_and_halt_installation_on_error "${?}" "Unable to copy deployment.json to hostOS config partition." - echo "* Copying NNS public key to hostOS config partition..." - cp /data/nns_public_key.pem /media/ - log_and_halt_installation_on_error "${?}" "Unable to copy NNS public key to hostOS config partition." + use_nns_public_key=$(get_config_value '.icos_settings.use_nns_public_key') + if [[ "${use_nns_public_key,,}" == "true" ]]; then + if [ -f "/data/nns_public_key.pem" ]; then + cp /data/nns_public_key.pem /media/ + log_and_halt_installation_on_error "${?}" "Unable to copy NNS public key to hostOS config partition." + else + log_and_halt_installation_on_error "1" "use_nns_public_key set to true but not found." + fi + else + log_and_halt_installation_on_error "1" "use_nns_public_key must be set to true." + fi + + echo "* Converting 'config.json' to hostOS config file 'config-hostos.json'..." + /opt/ic/bin/config generate-hostos-config + log_and_halt_installation_on_error "${?}" "Unable to generate hostos configuration." + + echo "* Copying 'config-hostos.json' to hostOS config partition..." + if [ -f "/var/ic/config/config-hostos.json" ]; then + cp /var/ic/config/config-hostos.json /media/config.json + log_and_halt_installation_on_error "${?}" "Unable to copy 'config-hostos.json' to hostOS config partition." + else + log_and_halt_installation_on_error "1" "Configuration file 'config-hostos.json' does not exist." + fi } function insert_hsm_if_necessary() { diff --git a/ic-os/components/setupos-scripts/setupos.sh b/ic-os/components/setupos-scripts/setupos.sh index 70c860207bf..622dc3973fd 100755 --- a/ic-os/components/setupos-scripts/setupos.sh +++ b/ic-os/components/setupos-scripts/setupos.sh @@ -39,6 +39,7 @@ main() { log_start "$(basename $0)" start_setupos /opt/ic/bin/check-setupos-age.sh + /opt/ic/bin/check-config.sh /opt/ic/bin/check-hardware.sh /opt/ic/bin/check-network.sh if kernel_cmdline_bool_default_true ic.setupos.perform_installation; then diff --git a/ic-os/components/setupos.bzl b/ic-os/components/setupos.bzl index f5bf1323bef..b60e576cd8c 100644 --- a/ic-os/components/setupos.bzl +++ b/ic-os/components/setupos.bzl @@ -8,7 +8,7 @@ component_files = { # setupos-scripts Label("//ic-os/components/setupos-scripts:check-setupos-age.sh"): "/opt/ic/bin/check-setupos-age.sh", - Label("//ic-os/components/setupos-scripts:config.sh"): "/opt/ic/bin/config.sh", + Label("//ic-os/components/setupos-scripts:check-config.sh"): "/opt/ic/bin/check-config.sh", Label("//ic-os/components/setupos-scripts:setup-hostos-config.sh"): "/opt/ic/bin/setup-hostos-config.sh", Label("//ic-os/components/setupos-scripts:setup-disk.sh"): "/opt/ic/bin/setup-disk.sh", Label("//ic-os/components/setupos-scripts:functions.sh"): "/opt/ic/bin/functions.sh", @@ -31,9 +31,9 @@ component_files = { # misc Label("misc/logging.sh"): "/opt/ic/bin/logging.sh", + Label("misc/config/setupos/config.sh"): "/opt/ic/bin/config.sh", Label("misc/chrony/chrony.conf"): "/etc/chrony/chrony.conf", Label("misc/chrony/chrony-var.service"): "/etc/systemd/system/chrony-var.service", - Label("misc/fetch-property.sh"): "/opt/ic/bin/fetch-property.sh", Label("misc/serial-getty@/setupos/override.conf"): "/etc/systemd/system/serial-getty@.service.d/override.conf", Label("monitoring/journald.conf"): "/etc/systemd/journald.conf", diff --git a/ic-os/guestos/docs/Boot.adoc b/ic-os/guestos/docs/Boot.adoc index 6c94e394e59..bffa12bce7e 100644 --- a/ic-os/guestos/docs/Boot.adoc +++ b/ic-os/guestos/docs/Boot.adoc @@ -57,7 +57,7 @@ not held in +/etc/fstab+ but is generated by the shell script Afterwards, the first three partitions are mounted as +/boot/efi+, +/boot/grub+ and +/boot/config+, respectively. The +config+ partition is used as (small) store for data that is preserved across upgrades -and is available at early boot time already (see link:ConfigStore{outfilesuffix}[config store]). +and is available at early boot time already. == Save machine-id @@ -167,8 +167,8 @@ depends on mount of all filesystems. This is only executed once on first boot after provisioning. It looks for a "virtual USB stick" attached to the VM that contains a tar file with initial configuration -for parts of the system (see link:ConfigStore{outfilesuffix}[config store] for a description). Required -files in the +config+ partition as well as payload store are created. +for parts of the system. Required files in the +config+ partition as well as +payload store are created. == Deploy updated ssh account keys diff --git a/ic-os/guestos/docs/DiskLayout.adoc b/ic-os/guestos/docs/DiskLayout.adoc index 8a690680b9d..13dbcbd99f5 100644 --- a/ic-os/guestos/docs/DiskLayout.adoc +++ b/ic-os/guestos/docs/DiskLayout.adoc @@ -41,8 +41,6 @@ tampering). == *config* System config store Contains the config store persisted across system upgrades. -See link:ConfigStore{outfilesuffix}[config store] for a -specification of its contents. == *A_boot* / *B_boot* Boot partition for system A/B diff --git a/ic-os/guestos/docs/README.adoc b/ic-os/guestos/docs/README.adoc index 4caa2719d2c..92e95a07169 100644 --- a/ic-os/guestos/docs/README.adoc +++ b/ic-os/guestos/docs/README.adoc @@ -3,7 +3,6 @@ Refer to detailed documentation on: * link:DiskLayout{outfilesuffix}[Disk layout] -* link:ConfigStore{outfilesuffix}[GuestOS config store] * link:Boot{outfilesuffix}[Boot sequence] * link:SELinux{outfilesuffix}[SELinux security policy] * link:Interface{outfilesuffix}[GuestOS input/output interface] \ No newline at end of file diff --git a/ic-os/setupos/defs.bzl b/ic-os/setupos/defs.bzl index 325ebb995fa..6483645fd41 100644 --- a/ic-os/setupos/defs.bzl +++ b/ic-os/setupos/defs.bzl @@ -31,6 +31,7 @@ def image_deps(mode, _malicious = False): "bootfs": {}, "rootfs": { "//rs/ic_os/release:setupos_tool": "/opt/ic/bin/setupos_tool:0755", + "//rs/ic_os/release:config": "/opt/ic/bin/config:0755", }, # Set various configuration values diff --git a/rs/ic_os/config/BUILD.bazel b/rs/ic_os/config/BUILD.bazel index 57f6e9aed83..0207b75976d 100644 --- a/rs/ic_os/config/BUILD.bazel +++ b/rs/ic_os/config/BUILD.bazel @@ -35,6 +35,10 @@ rust_library( exclude = ["src/main.rs"], ), crate_name = "config", + visibility = [ + "//rs:ic-os-pkg", + "//rs:system-tests-pkg", + ], deps = DEPENDENCIES, ) diff --git a/rs/ic_os/config/src/config_ini.rs b/rs/ic_os/config/src/config_ini.rs index 12ea4b9f2b3..2c697d231fd 100644 --- a/rs/ic_os/config/src/config_ini.rs +++ b/rs/ic_os/config/src/config_ini.rs @@ -89,7 +89,10 @@ pub fn get_config_ini_settings(config_file_path: &Path) -> Result Result<()> { Some(Commands::CreateSetuposConfig { config_ini_path, deployment_json_path, - use_nns_public_key, - use_ssh_authorized_keys, - use_node_operator_private_key, setupos_config_json_path, }) => { // get config.ini settings @@ -209,25 +197,28 @@ pub fn main() -> Result<()> { let mgmt_mac = resolve_mgmt_mac(deployment_json_settings.deployment.mgmt_mac)?; - let node_reward_type = node_reward_type.expect("Node reward type is required."); - - let node_reward_type_pattern = Regex::new(r"^type[0-9]+(\.[0-9])?$")?; - if !node_reward_type_pattern.is_match(&node_reward_type) { - anyhow::bail!( - "Invalid node_reward_type '{}'. It must match the pattern ^type[0-9]+(\\.[0-9])?$", - node_reward_type - ); + if let Some(ref node_reward_type) = node_reward_type { + let node_reward_type_pattern = Regex::new(r"^type[0-9]+(\.[0-9])?$")?; + if !node_reward_type_pattern.is_match(node_reward_type) { + anyhow::bail!( + "Invalid node_reward_type '{}'. It must match the pattern ^type[0-9]+(\\.[0-9])?$", + node_reward_type + ); + } + } else { + println!("Node reward type is not set. Skipping validation."); } let icos_settings = ICOSSettings { - node_reward_type: Some(node_reward_type), + node_reward_type, mgmt_mac, deployment_environment: deployment_json_settings.deployment.name.parse()?, logging: Logging::default(), - use_nns_public_key, + use_nns_public_key: Path::new("/data/nns_public_key.pem").exists(), nns_urls: deployment_json_settings.nns.url.clone(), - use_node_operator_private_key, - use_ssh_authorized_keys, + use_node_operator_private_key: Path::new("/config/node_operator_private_key.pem") + .exists(), + use_ssh_authorized_keys: Path::new("/config/ssh_authorized_keys").exists(), icos_dev_settings: ICOSDevSettings::default(), }; diff --git a/rs/ic_os/network/src/lib.rs b/rs/ic_os/network/src/lib.rs index 6db2156fda2..dcb1f5fa5f6 100644 --- a/rs/ic_os/network/src/lib.rs +++ b/rs/ic_os/network/src/lib.rs @@ -1,10 +1,11 @@ use std::path::Path; use std::process::Command; -use anyhow::{Context, Result}; +use anyhow::{anyhow, Context, Result}; use regex::Regex; -use crate::systemd::generate_systemd_config_files; +use crate::systemd::{generate_systemd_config_files, generate_systemd_config_files_new_config}; +use config_types::{Ipv6Config, NetworkSettings}; use deterministic_ips::MacAddr6Ext; use info::NetworkInfo; use macaddr::MacAddr6; @@ -13,6 +14,35 @@ pub mod info; pub mod interfaces; pub mod systemd; +/// Write SetupOS or HostOS systemd network configuration. +/// Requires superuser permissions to run `ipmitool` and write to the systemd directory +/// TODO(NODE-1466): Consolidate generate_network_config_new_config and generate_network_config +pub fn generate_network_config_new_config( + network_settings: &NetworkSettings, + generated_mac: &MacAddr6, + output_directory: &Path, +) -> Result<()> { + eprintln!("Generating IPv6 address"); + + match &network_settings.ipv6_config { + Ipv6Config::RouterAdvertisement => { + Err(anyhow!("IC-OS router advertisement is not yet supported")) + } + Ipv6Config::Fixed(_) => Err(anyhow!("Fixed IP configuration is not yet supported")), + Ipv6Config::Deterministic(ipv6_config) => { + let ipv6_address = generated_mac.calculate_slaac(&ipv6_config.prefix)?; + eprintln!("Using IPv6 address: {ipv6_address}"); + + generate_systemd_config_files_new_config( + output_directory, + ipv6_config, + Some(generated_mac), + &ipv6_address, + ) + } + } +} + /// Write SetupOS or HostOS systemd network configuration. /// Requires superuser permissions to run `ipmitool` and write to the systemd directory pub fn generate_network_config( diff --git a/rs/ic_os/network/src/systemd.rs b/rs/ic_os/network/src/systemd.rs index f1b3a76364f..7e55c0b446b 100644 --- a/rs/ic_os/network/src/systemd.rs +++ b/rs/ic_os/network/src/systemd.rs @@ -1,3 +1,4 @@ +use std::cmp::Reverse; use std::fs::{create_dir_all, write}; use std::net::Ipv6Addr; use std::path::Path; @@ -7,6 +8,7 @@ use anyhow::{Context, Result}; use crate::info::NetworkInfo; use crate::interfaces::{get_interfaces, has_ipv6_connectivity, Interface}; +use config_types::DeterministicIpv6Config; use macaddr::MacAddr6; pub static DEFAULT_SYSTEMD_NETWORK_DIR: &str = "/run/systemd/network"; @@ -74,6 +76,54 @@ pub fn restart_systemd_networkd() { // Explicitly don't care about return code status... } +// TODO(NODE-1466): Consolidate generate_systemd_config_files_new_config and generate_and_write_systemd_files +pub fn generate_systemd_config_files_new_config( + output_directory: &Path, + ipv6_config: &DeterministicIpv6Config, + generated_mac: Option<&MacAddr6>, + ipv6_address: &Ipv6Addr, +) -> Result<()> { + let mut interfaces = get_interfaces()?; + interfaces.sort_by_key(|v| Reverse(v.speed_mbps)); + eprintln!("Interfaces sorted decending by speed: {:?}", interfaces); + + let ping_target = ipv6_config.gateway.to_string(); + + let fastest_interface = interfaces + .iter() + .find(|i| { + match has_ipv6_connectivity(i, ipv6_address, ipv6_config.prefix_length, &ping_target) { + Ok(result) => result, + Err(e) => { + eprintln!("Error testing connectivity on {}: {}", &i.name, e); + false + } + } + }) + .context("Could not find any network interfaces")?; + + eprintln!("Using fastest interface: {:?}", fastest_interface); + + // Format the IP address to include the subnet length. See `man systemd.network`. + let ipv6_address = format!( + "{}/{}", + &ipv6_address.to_string(), + ipv6_config.prefix_length + ); + generate_and_write_systemd_files( + output_directory, + fastest_interface, + generated_mac, + &ipv6_address, + &ipv6_config.gateway.to_string(), + )?; + + println!("Restarting systemd networkd"); + restart_systemd_networkd(); + + Ok(()) +} + fn generate_and_write_systemd_files( output_directory: &Path, interface: &Interface, @@ -120,8 +170,7 @@ pub fn generate_systemd_config_files( ipv6_address: &Ipv6Addr, ) -> Result<()> { let mut interfaces = get_interfaces()?; - interfaces.sort_by_key(|v| v.speed_mbps); - interfaces.reverse(); + interfaces.sort_by_key(|v| Reverse(v.speed_mbps)); eprintln!("Interfaces sorted decending by speed: {:?}", interfaces); let ping_target = network_info.ipv6_gateway.to_string(); diff --git a/rs/ic_os/os_tools/setupos_tool/src/main.rs b/rs/ic_os/os_tools/setupos_tool/src/main.rs index 06eb78cbd9a..0d7ea1aa2b3 100644 --- a/rs/ic_os/os_tools/setupos_tool/src/main.rs +++ b/rs/ic_os/os_tools/setupos_tool/src/main.rs @@ -1,16 +1,14 @@ use std::path::Path; -use anyhow::{anyhow, Context, Result}; +use anyhow::{anyhow, Result}; use clap::{Parser, Subcommand}; -use config::config_ini::config_map_from_path; -use config::deployment_json::get_deployment_settings; -use config::{DEFAULT_SETUPOS_CONFIG_INI_FILE_PATH, DEFAULT_SETUPOS_DEPLOYMENT_JSON_PATH}; +use config::{deserialize_config, DEFAULT_SETUPOS_CONFIG_OBJECT_PATH}; +use config_types::{Ipv6Config, SetupOSConfig}; use deterministic_ips::node_type::NodeType; use deterministic_ips::{calculate_deterministic_mac, IpVariant, MacAddr6Ext}; -use network::info::NetworkInfo; +use network::generate_network_config_new_config; use network::systemd::DEFAULT_SYSTEMD_NETWORK_DIR; -use network::{generate_network_config, resolve_mgmt_mac}; use utils::to_cidr; #[derive(Subcommand)] @@ -29,12 +27,8 @@ pub enum Commands { #[derive(Parser)] struct SetupOSArgs { - #[arg(short, long, default_value_t = DEFAULT_SETUPOS_CONFIG_INI_FILE_PATH.to_string(), value_name = "FILE")] - config: String, - - #[arg(short, long, default_value_t = DEFAULT_SETUPOS_DEPLOYMENT_JSON_PATH.to_string(), value_name = "FILE")] - /// deployment.json file path - deployment_file: String, + #[arg(short, long, default_value_t = DEFAULT_SETUPOS_CONFIG_OBJECT_PATH.to_string(), value_name = "FILE")] + setupos_config_object_path: String, #[command(subcommand)] command: Option, @@ -50,61 +44,56 @@ pub fn main() -> Result<()> { match opts.command { Some(Commands::GenerateNetworkConfig { output_directory }) => { - let config_map = config_map_from_path(Path::new(&opts.config)).context(format!( - "Failed to get config.ini settings for path: {}", - &opts.config - ))?; - eprintln!("Using config: {:?}", config_map); - - let network_info = NetworkInfo::from_config_map(&config_map)?; - eprintln!("Network info config: {:?}", &network_info); + let setupos_config: SetupOSConfig = + deserialize_config(&opts.setupos_config_object_path)?; - let deployment_settings = get_deployment_settings(Path::new(&opts.deployment_file)) - .context(format!( - "Failed to get deployment settings for file: {}", - &opts.deployment_file - ))?; - eprintln!("Deployment config: {:?}", deployment_settings); + eprintln!( + "Network settings config: {:?}", + &setupos_config.network_settings + ); - let mgmt_mac = resolve_mgmt_mac(deployment_settings.deployment.mgmt_mac)?; - let deployment_environment = deployment_settings.deployment.name.parse()?; let generated_mac = calculate_deterministic_mac( - &mgmt_mac, - deployment_environment, + &setupos_config.icos_settings.mgmt_mac, + setupos_config.icos_settings.deployment_environment, IpVariant::V6, NodeType::SetupOS, ); - eprintln!("Using generated mac (unformatted) {}", generated_mac); + eprintln!("Using generated mac {}", generated_mac); - generate_network_config(&network_info, &generated_mac, Path::new(&output_directory)) + generate_network_config_new_config( + &setupos_config.network_settings, + &generated_mac, + Path::new(&output_directory), + ) } Some(Commands::GenerateIpv6Address { node_type }) => { - let config_map = config_map_from_path(Path::new(&opts.config)).context(format!( - "Failed to get config.ini settings for path: {}", - &opts.config - ))?; - eprintln!("Using config: {:?}", config_map); - - let network_info = NetworkInfo::from_config_map(&config_map)?; - eprintln!("Network info config: {:?}", &network_info); + let setupos_config: SetupOSConfig = + deserialize_config(&opts.setupos_config_object_path)?; - let deployment_settings = get_deployment_settings(Path::new(&opts.deployment_file)) - .context(format!( - "Failed to get deployment settings for file: {}", - &opts.deployment_file - ))?; - eprintln!("Deployment config: {:?}", deployment_settings); + eprintln!( + "Network settings config: {:?}", + &setupos_config.network_settings + ); - let mgmt_mac = resolve_mgmt_mac(deployment_settings.deployment.mgmt_mac)?; - let deployment_environment = deployment_settings.deployment.name.parse()?; let generated_mac = calculate_deterministic_mac( - &mgmt_mac, - deployment_environment, + &setupos_config.icos_settings.mgmt_mac, + setupos_config.icos_settings.deployment_environment, IpVariant::V6, node_type, ); - let ipv6_address = generated_mac.calculate_slaac(&network_info.ipv6_prefix)?; - println!("{}", to_cidr(ipv6_address, network_info.ipv6_subnet)); + eprintln!("Using generated mac address {}", generated_mac); + + let Ipv6Config::Deterministic(ipv6_config) = + &setupos_config.network_settings.ipv6_config + else { + return Err(anyhow!( + "Ipv6Config is not of type Deterministic. Cannot generate IPv6 address." + )); + }; + + let ipv6_address = generated_mac.calculate_slaac(&ipv6_config.prefix)?; + println!("{}", to_cidr(ipv6_address, ipv6_config.prefix_length)); + Ok(()) } None => Err(anyhow!(