-
Notifications
You must be signed in to change notification settings - Fork 803
Add API to manage SSH authorized keys on Home Assistant OS #7039
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
agners
wants to merge
6
commits into
main
Choose a base branch
from
os-ssh-authorized-keys
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from 5 commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
f82109d
Add API to manage SSH authorized keys on Home Assistant OS
agners 8ab0fe2
Delegate SSH key validation to OS Agent
agners bd04e62
Support OS Agent releases before 1.10.0
agners a39f211
Merge branch 'main' into os-ssh-authorized-keys
agners 9e567b1
Split SSH authorized keys API into add and clear endpoints
agners dbe711c
Add endpoint to list SSH authorized keys
agners File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,10 +15,12 @@ | |
| from ..exceptions import ( | ||
| DBusError, | ||
| DBusNotConnectedError, | ||
| HassOSError, | ||
| HassOSJobError, | ||
| HassOSSlotNotFound, | ||
| HassOSSlotUpdateError, | ||
| HassOSUpdateError, | ||
| HostError, | ||
| ) | ||
| from ..jobs.const import JobConcurrency, JobCondition | ||
| from ..jobs.decorator import Job | ||
|
|
@@ -27,6 +29,16 @@ | |
|
|
||
| _LOGGER: logging.Logger = logging.getLogger(__name__) | ||
|
|
||
| # SSH service on Home Assistant OS consuming /root/.ssh/authorized_keys | ||
| DROPBEAR_SERVICE = "dropbear.service" | ||
|
|
||
| # OS Agent releases before this return the os.Remove error when clearing an | ||
| # already absent authorized_keys file (inverted error check) | ||
| CLEAR_SSH_AUTH_KEYS_FIXED_VERSION = AwesomeVersion("1.10.0") | ||
| CLEAR_SSH_AUTH_KEYS_MISSING_FILE_ERROR = ( | ||
| "remove /root/.ssh/authorized_keys: no such file or directory" | ||
| ) | ||
|
|
||
|
|
||
| @dataclass(slots=True, frozen=True) | ||
| class SlotStatus: | ||
|
|
@@ -501,3 +513,58 @@ async def set_boot_slot(self, boot_name: str) -> None: | |
|
|
||
| _LOGGER.info("Rebooting into new boot slot now") | ||
| await self.sys_host.control.reboot() | ||
|
|
||
| @Job( | ||
| name="os_manager_add_ssh_authorized_key", | ||
| conditions=[JobCondition.HAOS], | ||
| on_condition=HassOSJobError, | ||
| internal=True, | ||
| ) | ||
| async def add_ssh_authorized_key(self, key: str) -> None: | ||
| """Add an SSH authorized key for root on the host and start dropbear. | ||
|
|
||
| OS Agent validates the key since 1.10.0; older releases append it to | ||
| the authorized_keys file as submitted. | ||
| """ | ||
| _LOGGER.info("Adding SSH authorized key on host") | ||
| try: | ||
| await self.sys_dbus.agent.system.add_ssh_auth_key(key) | ||
| except DBusError as err: | ||
| raise HassOSError( | ||
| f"Can't add SSH authorized key: {err!s}", _LOGGER.error | ||
| ) from err | ||
|
|
||
| # dropbear on Home Assistant OS is gated by | ||
| # ConditionFileNotEmpty=/root/.ssh/authorized_keys, which systemd only | ||
| # evaluates when the unit starts. A running dropbear re-reads the file | ||
| # on every authentication attempt and starting an active unit is a | ||
| # no-op, so only the stopped service needs this. | ||
| try: | ||
| await self.sys_host.services.start(DROPBEAR_SERVICE) | ||
| except (HostError, DBusError) as err: | ||
| raise HassOSError( | ||
| f"SSH authorized key written, but can't start dropbear: {err!s}", | ||
| _LOGGER.error, | ||
| ) from err | ||
|
|
||
| @Job( | ||
| name="os_manager_clear_ssh_authorized_keys", | ||
| conditions=[JobCondition.HAOS], | ||
| on_condition=HassOSJobError, | ||
| internal=True, | ||
| ) | ||
| async def clear_ssh_authorized_keys(self) -> None: | ||
| """Remove all SSH authorized keys of root on the host.""" | ||
| _LOGGER.info("Clearing SSH authorized keys on host") | ||
| try: | ||
| await self.sys_dbus.agent.system.clear_ssh_auth_keys() | ||
| except DBusError as err: | ||
| # On affected OS Agent releases the missing-file error is the | ||
| # empty state clearing aims for, so treat it as success there. | ||
| if ( | ||
| self.sys_dbus.agent.version >= CLEAR_SSH_AUTH_KEYS_FIXED_VERSION | ||
| or CLEAR_SSH_AUTH_KEYS_MISSING_FILE_ERROR not in str(err) | ||
| ): | ||
| raise HassOSError( | ||
| f"Can't clear SSH authorized keys: {err!s}", _LOGGER.error | ||
| ) from err | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this stop dropbear service after a successful key clear? Since we've reset to initial state where the authorized key file is empty. |
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we make this a core only endpoint rather then a manager one? Putting it in manager would give many apps the ability to call it. I know its hardly the only attack avenue if you consider the idea of a malicious app but it still just doesn't seem like capability we want to allow apps to do.
The downside would be the SSH app also can't call it which is probably the one app we'd prefer to allow. But as long as we make the proposed UI this seems like an acceptable situation to only allow host ssh key management from HA UI and the host shell itself.