Skip to content

Conversation

@ElanHasson
Copy link
Collaborator

Summary

  • Adds local SSH access feature (SSH_ALLOW_LOCAL) allowing localaccess group members to SSH between users on localhost
  • Prevents MOTD infinite recursion when openclaw wrapper uses SSH by setting MOTD_SKIP env var
  • Dynamically generates SSH configs (sshd_config.d and ssh_config.d) instead of static files
  • Adds comprehensive documentation for SSH, user access, and system architecture

Changes

  • 13-setup-ssh-local: New init script that sets up local SSH access with key rotation
  • ssh-rotate-local-key: Script to rotate SSH keys for localaccess group members
  • 80-openclaw: MOTD script now checks MOTD_SKIP to prevent recursion, calls binary directly
  • openclaw wrapper: Exports MOTD_SKIP=1 before SSH calls
  • permissions.yaml: Added ssh_config.d entries
  • Documentation: Added ssh.md, user-access.md, system-architecture.md

Test plan

  • Verify SSH_ALLOW_LOCAL=true enables local SSH access
  • Verify SSH_ALLOW_LOCAL=false cleans up and disables local access
  • Verify MOTD displays without infinite recursion
  • Verify openclaw health works from root user via SSH
  • Verify key rotation works via cron

@ElanHasson ElanHasson requested a review from ssaengs February 1, 2026 11:36
@gitguardian
Copy link

gitguardian bot commented Feb 1, 2026

️✅ There are no secrets present in this pull request anymore.

If these secrets were true positive and are still valid, we highly recommend you to revoke them.
While these secrets were previously flagged, we no longer have a reference to the
specific commits where they were detected. Once a secret has been leaked into a git
repository, you should consider it compromised, even if it was deleted immediately.
Find here more information about risks.


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

@ElanHasson
Copy link
Collaborator Author

I resolved as dummy secret in Git Guardian

- Added `broadcast_prefix` function to `env-utils.sh` for broadcasting PUBLIC_ vars to all prefix directories.
- Updated `environment.md` to document new `broadcast_prefix` function and clarify usage of PUBLIC_ variables.
- Introduced `ssh.md` for comprehensive SSH configuration details, including environment variables and init scripts.
- Created `system-architecture.md` to outline the container's boot process and service management using s6-overlay.
- Added `user-access.md` to explain user roles and access methods within the container.
- Implemented local SSH access setup in `13-setup-ssh-local`, including key rotation and group management.
- Updated permissions in `permissions.yaml` to reflect new SSH and environment variable access rules.
- Created `ssh-rotate-local-key` script for daily SSH key rotation for localaccess group members.
- Modified `openclaw` wrapper to support local SSH access and improved environment sourcing.
- Added cron job for daily SSH key rotation.
…testing

- Add SSH test helper functions to lib.sh for local and external connections
- Expose port 2222 in compose.yaml for external SSH testing
- Generate throwaway SSH keypair in CI for external SSH tests
- Add Tailscale sidecar container for testing SSH over Tailscale network
- Create ssh-tailscale and ssh-tailscale-persistence test configurations
- Add thorough negative test cases:
  - Password authentication disabled
  - Non-localaccess users denied
  - Unauthorized keys denied
  - External root login denied
@ElanHasson ElanHasson force-pushed the feature/ssh-allow-local branch from 4a38d29 to ac48911 Compare February 1, 2026 22:11
Remove S6_BEHAVIOUR_IF_STAGE2_FAILS from example configs and set it
along with S6_VERBOSITY=2 at the test scenario level for debugging.
- ssh-enabled: Split 01-service.sh into 01-service.sh, 05-config.sh,
  06-local-ssh.sh, 07-external-ssh.sh, 08-security.sh
- ssh-tailscale: Split test.sh into 01-local-ssh.sh, 02-external-ssh.sh,
  03-tailscale-ssh.sh, 04-security.sh
- ssh-tailscale-persistence: Split test.sh into 01-ssh.sh, 02-tailscale.sh,
  03-persistence.sh, 04-security.sh
- authorized_keys: Accept both 600 and 644 permissions
- Tailscale tests: Skip gracefully when network connectivity fails
- Add shopt -s globstar nullglob to apply_permissions() so patterns like
  /**/.ssh/authorized_keys work correctly for recursive matching
- Restore original shell options after function completes
- Revert lenient 644 permission check in 02-authorized-keys.sh test,
  now correctly expects 600 per permissions.yaml
The case pattern *\* doesn't correctly match literal asterisks in
paths. Changed to *'*'* which properly detects glob patterns like
/**/.ssh/authorized_keys for chmod/chown expansion.
- Increase wait_for_process default from 5 attempts (2.5s) to 30 (30s)
- Add wait_for_process before SSH operations in all tests that need it:
  - ssh-enabled: 03-connectivity, 06-local-ssh, 07-external-ssh, 08-security
  - ssh-tailscale: 02-external-ssh, 04-security
  - ssh-tailscale-persistence: 04-security
- Fixes CI failures where tests ran before sshd was fully initialized
- New test 09-app-platform-deploy.sh that deploys to App Platform
- Uses doctl to create app from app-ssh-local.spec.yaml
- Modifies spec with unique name and current branch via jq
- Cleans up app via trap on exit
- Install doctl via digitalocean/action-doctl@v2 for ssh configs
- Create DOCR registry with professional plan
- Tag and push local openclaw-test:latest image to DOCR
- Update app spec to use DOCR image instead of git-based build
- Output registry_name for cleanup step
- Add cleanup step to delete DOCR registry after test
- Registry named openclaw-pr-{number} for PRs, openclaw-main for main
- Reuse existing registry across pushes to same PR
- New cleanup-pr.yml workflow deletes registry when PR closes
- Add --wait to skip polling loop
- Fix jq queries to use .[0] for array responses
- Move dump_app_state to workflow cleanup step
- Test openclaw SSH denial separately from ubuntu/root success
Instead of using /etc/ssh/authorized_keys (system-wide), write all
local access public keys to each user's ~/.ssh/authorized_keys with
markers. This simplifies the sshd config and keeps keys in standard
locations.

Also adds SSH_AUTHORIZED_USERS to App Platform deploy spec so that
external SSH access works (CI SSH key written to ubuntu's authorized_keys).
Updated comment to improve clarity.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant