diff --git a/api/src/opentrons/config/reset.py b/api/src/opentrons/config/reset.py index ccba3b2fdcb..42a2b606106 100644 --- a/api/src/opentrons/config/reset.py +++ b/api/src/opentrons/config/reset.py @@ -49,6 +49,7 @@ class ResetOptionId(str, Enum): ResetOptionId.pipette_offset, ResetOptionId.tip_length_calibrations, ResetOptionId.runs_history, + ResetOptionId.authorized_keys, ] _FLEX_RESET_OPTIONS = [ ResetOptionId.boot_scripts, diff --git a/robot-server/tests/integration/test_settings_reset_options.tavern.yaml b/robot-server/tests/integration/test_settings_reset_options.tavern.yaml index 7dc49479bba..4025206afee 100644 --- a/robot-server/tests/integration/test_settings_reset_options.tavern.yaml +++ b/robot-server/tests/integration/test_settings_reset_options.tavern.yaml @@ -27,6 +27,10 @@ stages: - id: runsHistory name: Clear Runs History description: !re_search 'Erase this device''s stored history of protocols and runs.' + - id: authorizedKeys + name: SSH Authorized Keys + description: !re_search 'Clear the ssh authorized keys' + --- test_name: POST Reset bootScripts option marks: @@ -123,6 +127,32 @@ stages: message: "gripperOffsetCalibrations is not a valid reset option." errorCode: "4000" --- +test_name: POST Reset authorizedKeys option +marks: + - usefixtures: + - ot2_server_base_url +stages: + - name: POST Reset authorizedKeys true + request: + url: '{ot2_server_base_url}/settings/reset' + method: POST + json: + authorizedKeys: true + response: + status_code: 200 + json: + message: "Options 'authorized_keys' were reset" + - name: POST Reset authorizedKeys false + request: + url: '{ot2_server_base_url}/settings/reset' + method: POST + json: + authorizedKeys: false + response: + status_code: 200 + json: + message: 'Nothing to do' +--- test_name: POST Reset non existant option marks: - usefixtures: diff --git a/robot-server/tests/service/legacy/routers/test_settings.py b/robot-server/tests/service/legacy/routers/test_settings.py index 0f96946dac0..05f4a187045 100644 --- a/robot-server/tests/service/legacy/routers/test_settings.py +++ b/robot-server/tests/service/legacy/routers/test_settings.py @@ -514,6 +514,7 @@ def test_available_resets(api_client): "bootScripts", "tipLengthCalibrations", "runsHistory", + "authorizedKeys", ] ) == sorted([item["id"] for item in options_list]) @@ -551,6 +552,7 @@ async def mock_get_persistence_resetter() -> PersistenceResetter: "pipetteOffsetCalibrations": False, "tipLengthCalibrations": False, "runsHistory": False, + "authorizedKeys": False, }, set(), ], @@ -562,6 +564,7 @@ async def mock_get_persistence_resetter() -> PersistenceResetter: "tipLengthCalibrations": True, "deckCalibration": True, "runsHistory": True, + "authorizedKeys": True, # TODO(mm, 2023-08-04): Figure out how to test Flex-only options, # then add gripperOffsetCalibrations and onDeviceDisplay. }, @@ -575,8 +578,10 @@ async def mock_get_persistence_resetter() -> PersistenceResetter: # mark_directory_reset() being an async method, and api_client having # its own event loop that interferes with making this test async. ResetOptionId.runs_history, + ResetOptionId.authorized_keys, }, ], + [{"authorizedKeys": True}, {ResetOptionId.authorized_keys}], [{"bootScripts": True}, {ResetOptionId.boot_scripts}], [{"pipetteOffsetCalibrations": True}, {ResetOptionId.pipette_offset}], [{"tipLengthCalibrations": True}, {ResetOptionId.tip_length_calibrations}], diff --git a/update-server/otupdate/common/ssh_key_management.py b/update-server/otupdate/common/ssh_key_management.py index 8129322b80d..fa327806701 100644 --- a/update-server/otupdate/common/ssh_key_management.py +++ b/update-server/otupdate/common/ssh_key_management.py @@ -251,7 +251,8 @@ async def add_from_local(request: web.Request) -> web.Response: Path(root, file) for root, _, files in os.walk("/media") for file in files - if file.endswith(".pub") + # skip hidden files + if not file.startswith(".") and file.endswith(".pub") ] if not pub_keys: LOG.warning("No keys found") @@ -265,16 +266,20 @@ async def add_from_local(request: web.Request) -> web.Response: new_keys = list() with open(AUTHORIZED_KEYS, "a") as fh: for key in pub_keys: - with open(key, "r") as gh: - ssh_key = gh.read() - if "ssh-rsa" not in ssh_key: - LOG.warning(f"Invalid ssh public key: {key}") - continue - key_hash = hashlib.new("md5", ssh_key.encode()).hexdigest() - if not key_present(key_hash): - fh.write(f"{ssh_key}\n") - LOG.info(f"Added new rsa key: {key}") + try: + with open(key, "r") as gh: + ssh_key = gh.read().strip() + if "ssh-rsa" not in ssh_key and "ecdsa" not in ssh_key: + LOG.warning(f"Invalid ssh public key: {key}") + continue + key_hash = hashlib.new("md5", ssh_key.encode()).hexdigest() + if not key_present(key_hash): + fh.write(f"{ssh_key}\n") + LOG.info(f"Added new rsa key: {key}") new_keys.append(key_hash) + except Exception as e: + LOG.error(f"Could not process ssh public key: {key} {e}") + continue return web.json_response( # type: ignore[no-untyped-call,no-any-return] data={"message": f"Added {len(new_keys)} new keys", "key_md5": new_keys},