diff options
| author | Oleksandr Kuchmystyi <o.kuchmystyi@vyos.io> | 2026-04-24 17:30:32 +0300 |
|---|---|---|
| committer | Oleksandr Kuchmystyi <o.kuchmystyi@vyos.io> | 2026-04-28 10:40:59 +0300 |
| commit | 996a872eca8786ddd4335dd23dd9cb048ec11ffd (patch) | |
| tree | c6f67cb13f7a1dde25aedbdcd29b565d351815ff /src/migration-scripts/interfaces | |
| parent | 6bbd589ae563e4737845217710ff80c73a3b8fc4 (diff) | |
| download | vyos-1x-996a872eca8786ddd4335dd23dd9cb048ec11ffd.tar.gz vyos-1x-996a872eca8786ddd4335dd23dd9cb048ec11ffd.zip | |
migration: T8280: Fix auth file read for interfaces on first boot of new image
During first boot after installing a new VyOS image, migrations are
executed before the filesystem is fully initialized.
At this point `/config` is not yet bind-mounted, causing auth files
(certificates, keys, credentials) for OpenVPN, WireGuard and Ethernet
EAPoL interfaces under `/config/auth` to be inaccessible even though they
exist under the underlying `/opt/vyatta/etc/config/auth` path which is
available at all boot stages.
Add fallback logic to `read_auth_file()` to retry the file lookup under
`/opt/vyatta/etc/config/auth` when the file is not found under `/config/auth`.
Diffstat (limited to 'src/migration-scripts/interfaces')
| -rw-r--r-- | src/migration-scripts/interfaces/25-to-26 | 200 |
1 files changed, 78 insertions, 122 deletions
diff --git a/src/migration-scripts/interfaces/25-to-26 b/src/migration-scripts/interfaces/25-to-26 index e68a75d08..1f10e3dca 100644 --- a/src/migration-scripts/interfaces/25-to-26 +++ b/src/migration-scripts/interfaces/25-to-26 @@ -29,6 +29,7 @@ from vyos.pki import encode_dh_parameters from vyos.pki import encode_private_key from vyos.pki import verify_crl from vyos.utils.process import run +from vyos.utils.file import read_file def wrapped_pem_to_config_value(pem): out = [] @@ -38,20 +39,28 @@ def wrapped_pem_to_config_value(pem): out.append(line) return "".join(out) -def read_file_for_pki(config_auth_path): - full_path = os.path.join(AUTH_DIR, config_auth_path) - output = None +def read_auth_file(config_auth_path): + full_path = os.path.normpath(os.path.join(AUTH_DIR, config_auth_path)) + + # If the file is not found under `/config/auth`, it may be because the `/config` + # partition has not been bind-mounted yet during early boot migration execution. + # Fall back to the equivalent path under `/opt/vyatta/etc/config/auth` which + # is accessible at all boot stages. + if not os.path.isfile(full_path) and full_path.startswith(f'{AUTH_DIR}/'): + full_path = AUTH_DIR_FALLBACK + full_path[len(AUTH_DIR): ] if os.path.isfile(full_path): if not os.access(full_path, os.R_OK): - run(f'sudo chmod 644 {full_path}') + run(['sudo', 'chmod', '644', full_path]) + + return read_file(full_path) - with open(full_path, 'r') as f: - output = f.read() + return None - return output AUTH_DIR = '/config/auth' +AUTH_DIR_FALLBACK = '/opt/vyatta/etc/config/auth' + pki_base = ['pki'] def migrate(config: ConfigTree) -> None: @@ -69,7 +78,7 @@ def migrate(config: ConfigTree) -> None: config.set_tag(pki_base + ['openvpn', 'shared-secret']) key_file = config.return_value(base + [interface, 'shared-secret-key-file']) - key = read_file_for_pki(key_file) + key = read_auth_file(key_file) key_pki_name = f'{pki_name}_shared' if key: @@ -107,7 +116,7 @@ def migrate(config: ConfigTree) -> None: config.set_tag(pki_base + ['openvpn', 'shared-secret']) key_file = config.return_value(base + [interface, 'tls', 'auth-file']) - key = read_file_for_pki(key_file) + key = read_auth_file(key_file) key_pki_name = f'{pki_name}_auth' if key: @@ -142,7 +151,7 @@ def migrate(config: ConfigTree) -> None: config.set_tag(pki_base + ['openvpn', 'shared-secret']) key_file = config.return_value(base + [interface, 'tls', 'crypt-file']) - key = read_file_for_pki(key_file) + key = read_auth_file(key_file) key_pki_name = f'{pki_name}_crypt' if key: @@ -179,46 +188,41 @@ def migrate(config: ConfigTree) -> None: config.set_tag(pki_base + ['ca']) cert_file = config.return_value(x509_base + ['ca-cert-file']) - cert_path = os.path.join(AUTH_DIR, cert_file) - - if os.path.isfile(cert_path): - if not os.access(cert_path, os.R_OK): - run(f'sudo chmod 644 {cert_path}') - - with open(cert_path, 'r') as f: - certs_str = f.read() - certs_data = certs_str.split(CERT_BEGIN) - index = 1 - for cert_data in certs_data[1:]: - cert = load_certificate(CERT_BEGIN + cert_data, wrap_tags=False) - - if cert: - ca_certs[f'{pki_name}_{index}'] = cert - cert_pem = encode_certificate(cert) - - # Check if CA already exists - no need to check node existence as - # it is always created above when entering this context - ca_exists = None - for ca_name in config.list_nodes(pki_base + ['ca']): - ca_cert_path = pki_base + ['ca', ca_name, 'certificate'] - if not config.exists(ca_cert_path): - continue - - ca_base64 = config.return_value(ca_cert_path) - # Check for duplicate cert/key - and re-use if possible - if ca_base64 == wrapped_pem_to_config_value(cert_pem): - ca_exists = ca_name - break - - if ca_exists: - config.set(x509_base + ['ca-certificate'], value=ca_exists, replace=False) - else: - config.set(pki_base + ['ca', f'{pki_name}_{index}', 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) - config.set(x509_base + ['ca-certificate'], value=f'{pki_name}_{index}', replace=False) + certs_str = read_auth_file(cert_file) + + if certs_str: + certs_data = certs_str.split(CERT_BEGIN) + index = 1 + for cert_data in certs_data[1:]: + cert = load_certificate(CERT_BEGIN + cert_data, wrap_tags=False) + + if cert: + ca_certs[f'{pki_name}_{index}'] = cert + cert_pem = encode_certificate(cert) + + # Check if CA already exists - no need to check node existence as + # it is always created above when entering this context + ca_exists = None + for ca_name in config.list_nodes(pki_base + ['ca']): + ca_cert_path = pki_base + ['ca', ca_name, 'certificate'] + if not config.exists(ca_cert_path): + continue + + ca_base64 = config.return_value(ca_cert_path) + # Check for duplicate cert/key - and re-use if possible + if ca_base64 == wrapped_pem_to_config_value(cert_pem): + ca_exists = ca_name + break + + if ca_exists: + config.set(x509_base + ['ca-certificate'], value=ca_exists, replace=False) else: - print(f'Failed to migrate CA certificate on openvpn interface {interface}') + config.set(pki_base + ['ca', f'{pki_name}_{index}', 'certificate'], value=wrapped_pem_to_config_value(cert_pem)) + config.set(x509_base + ['ca-certificate'], value=f'{pki_name}_{index}', replace=False) + else: + print(f'Failed to migrate CA certificate on openvpn interface {interface}') - index += 1 + index += 1 else: print(f'Failed to migrate CA certificate on openvpn interface {interface}') @@ -230,24 +234,18 @@ def migrate(config: ConfigTree) -> None: config.set_tag(pki_base + ['ca']) crl_file = config.return_value(x509_base + ['crl-file']) - crl_path = os.path.join(AUTH_DIR, crl_file) - crl = None - crl_ca_name = None + crl_data = read_auth_file(crl_file) - if os.path.isfile(crl_path): - if not os.access(crl_path, os.R_OK): - run(f'sudo chmod 644 {crl_path}') - - with open(crl_path, 'r') as f: - crl_data = f.read() - crl = load_crl(crl_data, wrap_tags=False) + crl = load_crl(crl_data, wrap_tags=False) if crl_data else None + crl_ca_name = None - for ca_name, ca_cert in ca_certs.items(): - if verify_crl(crl, ca_cert): - crl_ca_name = ca_name - break + if crl: + for ca_name, ca_cert in ca_certs.items(): + if verify_crl(crl, ca_cert): + crl_ca_name = ca_name + break - if crl and crl_ca_name: + if crl_ca_name: crl_pem = encode_certificate(crl) # Check if CRL already exists - no need to check node @@ -277,16 +275,9 @@ def migrate(config: ConfigTree) -> None: config.set_tag(pki_base + ['certificate']) cert_file = config.return_value(x509_base + ['cert-file']) - cert_path = os.path.join(AUTH_DIR, cert_file) - cert = None + cert_data = read_auth_file(cert_file) - if os.path.isfile(cert_path): - if not os.access(cert_path, os.R_OK): - run(f'sudo chmod 644 {cert_path}') - - with open(cert_path, 'r') as f: - cert_data = f.read() - cert = load_certificate(cert_data, wrap_tags=False) + cert = load_certificate(cert_data, wrap_tags=False) if cert_data else None if cert: cert_pem = encode_certificate(cert) @@ -316,16 +307,9 @@ def migrate(config: ConfigTree) -> None: if config.exists(x509_base + ['key-file']): key_file = config.return_value(x509_base + ['key-file']) - key_path = os.path.join(AUTH_DIR, key_file) - key = None - - if os.path.isfile(key_path): - if not os.access(key_path, os.R_OK): - run(f'sudo chmod 644 {key_path}') + key_data = read_auth_file(key_file) - with open(key_path, 'r') as f: - key_data = f.read() - key = load_private_key(key_data, passphrase=None, wrap_tags=False) + key = load_private_key(key_data, passphrase=None, wrap_tags=False) if key_data else None if key: key_pem = encode_private_key(key, passphrase=None) @@ -356,16 +340,9 @@ def migrate(config: ConfigTree) -> None: config.set_tag(pki_base + ['dh']) dh_file = config.return_value(x509_base + ['dh-file']) - dh_path = os.path.join(AUTH_DIR, dh_file) - dh = None - - if os.path.isfile(dh_path): - if not os.access(dh_path, os.R_OK): - run(f'sudo chmod 644 {dh_path}') + dh_data = read_auth_file(dh_file) - with open(dh_path, 'r') as f: - dh_data = f.read() - dh = load_dh_parameters(dh_data, wrap_tags=False) + dh = load_dh_parameters(dh_data, wrap_tags=False) if dh_data else None if dh: dh_pem = encode_dh_parameters(dh) @@ -405,15 +382,15 @@ def migrate(config: ConfigTree) -> None: if config.exists(private_key_path): key_file = config.return_value(private_key_path) - full_key_path = f'/config/auth/wireguard/{key_file}/private.key' + full_key_path = f'{AUTH_DIR}/wireguard/{key_file}/private.key' + key_data = read_auth_file(full_key_path) - if not os.path.exists(full_key_path): + if not key_data: print(f'Could not find wireguard private key for migration on interface "{interface}"') continue - with open(full_key_path, 'r') as f: - key_data = f.read().strip() - config.set(private_key_path, value=key_data) + key_data = key_data.strip() + config.set(private_key_path, value=key_data) for peer in config.list_nodes(base + [interface, 'peer']): config.rename(base + [interface, 'peer', peer, 'pubkey'], 'public-key') @@ -435,16 +412,9 @@ def migrate(config: ConfigTree) -> None: config.set_tag(pki_base + ['ca']) cert_file = config.return_value(x509_base + ['ca-cert-file']) - cert_path = os.path.join(AUTH_DIR, cert_file) - cert = None + cert_data = read_auth_file(cert_file) - if os.path.isfile(cert_path): - if not os.access(cert_path, os.R_OK): - run(f'sudo chmod 644 {cert_path}') - - with open(cert_path, 'r') as f: - cert_data = f.read() - cert = load_certificate(cert_data, wrap_tags=False) + cert = load_certificate(cert_data, wrap_tags=False) if cert_data else None if cert: cert_pem = encode_certificate(cert) @@ -461,16 +431,9 @@ def migrate(config: ConfigTree) -> None: config.set_tag(pki_base + ['certificate']) cert_file = config.return_value(x509_base + ['cert-file']) - cert_path = os.path.join(AUTH_DIR, cert_file) - cert = None - - if os.path.isfile(cert_path): - if not os.access(cert_path, os.R_OK): - run(f'sudo chmod 644 {cert_path}') + cert_data = read_auth_file(cert_file) - with open(cert_path, 'r') as f: - cert_data = f.read() - cert = load_certificate(cert_data, wrap_tags=False) + cert = load_certificate(cert_data, wrap_tags=False) if cert_data else None if cert: cert_pem = encode_certificate(cert) @@ -483,16 +446,9 @@ def migrate(config: ConfigTree) -> None: if config.exists(x509_base + ['key-file']): key_file = config.return_value(x509_base + ['key-file']) - key_path = os.path.join(AUTH_DIR, key_file) - key = None - - if os.path.isfile(key_path): - if not os.access(key_path, os.R_OK): - run(f'sudo chmod 644 {key_path}') + key_data = read_auth_file(key_file) - with open(key_path, 'r') as f: - key_data = f.read() - key = load_private_key(key_data, passphrase=None, wrap_tags=False) + key = load_private_key(key_data, passphrase=None, wrap_tags=False) if key_data else None if key: key_pem = encode_private_key(key, passphrase=None) |
