├── images ├── ssh-workflow.png └── output-result.png ├── entrypoint.sh ├── Dockerfile ├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── LICENSE ├── action.yml ├── README.zh-tw.md └── README.md /images/ssh-workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adaptive/ssh-action/master/images/ssh-workflow.png -------------------------------------------------------------------------------- /images/output-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adaptive/ssh-action/master/images/output-result.png -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | export GITHUB="true" 6 | 7 | sh -c "/bin/drone-ssh $*" 8 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ghcr.io/appleboy/drone-ssh:1.7.0 2 | 3 | COPY entrypoint.sh /entrypoint.sh 4 | RUN chmod +x /entrypoint.sh 5 | ENTRYPOINT ["/entrypoint.sh"] 6 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: ssh-action 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: ['https://www.paypal.me/appleboy46'] 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Bo-Yi Wu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: 'SSH Remote Commands' 2 | description: 'Executing remote ssh commands' 3 | author: 'Bo-Yi Wu' 4 | inputs: 5 | host: 6 | description: 'SSH host address.' 7 | port: 8 | description: 'SSH port number.' 9 | default: "22" 10 | passphrase: 11 | description: 'Passphrase for the SSH key.' 12 | username: 13 | description: 'SSH username.' 14 | password: 15 | description: 'SSH password.' 16 | sync: 17 | description: 'Enable synchronous execution if multiple hosts are involved.' 18 | use_insecure_cipher: 19 | description: 'Include more ciphers by using insecure ciphers.' 20 | cipher: 21 | description: 'Allowed cipher algorithms. If unspecified, a sensible default is used.' 22 | timeout: 23 | description: 'Timeout duration for establishing SSH connection to the host.' 24 | default: "30s" 25 | command_timeout: 26 | description: 'Timeout duration for SSH commands execution.' 27 | default: "10m" 28 | key: 29 | description: 'Content of the SSH private key. For example, the raw content of ~/.ssh/id_rsa.' 30 | key_path: 31 | description: 'Path to the SSH private key file.' 32 | fingerprint: 33 | description: 'SHA256 fingerprint of the host public key.' 34 | proxy_host: 35 | description: 'SSH proxy host address.' 36 | proxy_port: 37 | description: 'SSH proxy port number.' 38 | default: "22" 39 | proxy_username: 40 | description: 'SSH proxy username.' 41 | proxy_password: 42 | description: 'SSH proxy password.' 43 | proxy_passphrase: 44 | description: 'SSH proxy key passphrase.' 45 | proxy_timeout: 46 | description: 'Timeout duration for establishing SSH connection to the proxy host.' 47 | default: "30s" 48 | proxy_key: 49 | description: 'Content of the SSH proxy private key. For example, the raw content of ~/.ssh/id_rsa.' 50 | proxy_key_path: 51 | description: 'Path to the SSH proxy private key file.' 52 | proxy_fingerprint: 53 | description: 'SHA256 fingerprint of the proxy host public key.' 54 | proxy_cipher: 55 | description: 'Allowed cipher algorithms for the proxy. If unspecified, a sensible default is used.' 56 | proxy_use_insecure_cipher: 57 | description: 'Include more ciphers for the proxy by using insecure ciphers.' 58 | script: 59 | description: 'Commands to be executed.' 60 | script_stop: 61 | description: 'Stop the script after the first failure.' 62 | envs: 63 | description: 'Environment variables to be passed to the shell script.' 64 | envs_format: 65 | description: 'Flexible configuration for environment value transfer.' 66 | debug: 67 | description: 'Enable debug mode.' 68 | allenvs: 69 | description: 'pass all environment variable to shell script.' 70 | runs: 71 | using: 'docker' 72 | image: 'Dockerfile' 73 | 74 | branding: 75 | icon: 'terminal' 76 | color: 'gray-dark' 77 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: remote ssh command 2 | on: [push] 3 | 4 | env: 5 | FOO: "BAR" 6 | BAR: "FOO" 7 | 8 | jobs: 9 | testing01: 10 | name: default flag testing 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: checkout 14 | uses: actions/checkout@v1 15 | 16 | - name: correct password but wrong key 17 | uses: ./ 18 | with: 19 | host: ${{ secrets.HOST }} 20 | username: ${{ secrets.USERNAME }} 21 | password: ${{ secrets.PASSWORD }} 22 | key: "1234" 23 | port: ${{ secrets.PORT }} 24 | script: whoami 25 | 26 | - name: wrong password but correct key 27 | uses: ./ 28 | with: 29 | host: ${{ secrets.HOST }} 30 | username: ${{ secrets.USERNAME }} 31 | password: "abcdef" 32 | key: ${{ secrets.KEY }} 33 | port: ${{ secrets.PORT }} 34 | script: whoami 35 | 36 | - name: executing remote ssh commands using password 37 | uses: ./ 38 | with: 39 | host: ${{ secrets.HOST }} 40 | username: ${{ secrets.USERNAME }} 41 | password: ${{ secrets.PASSWORD }} 42 | port: ${{ secrets.PORT }} 43 | script: whoami 44 | 45 | - name: executing remote ssh commands using ssh key 46 | uses: ./ 47 | with: 48 | host: ${{ secrets.HOST }} 49 | username: ${{ secrets.USERNAME }} 50 | key: ${{ secrets.KEY }} 51 | port: ${{ secrets.PORT }} 52 | script: whoami 53 | 54 | - name: multiple command 55 | uses: ./ 56 | with: 57 | host: ${{ secrets.HOST }} 58 | username: ${{ secrets.USERNAME }} 59 | key: ${{ secrets.KEY }} 60 | port: ${{ secrets.PORT }} 61 | script: | 62 | whoami 63 | ls -al 64 | 65 | - name: stop script if command error 66 | uses: ./ 67 | continue-on-error: true 68 | with: 69 | host: ${{ secrets.HOST }} 70 | username: ${{ secrets.USERNAME }} 71 | key: ${{ secrets.KEY }} 72 | port: ${{ secrets.PORT }} 73 | script_stop: true 74 | sync: true 75 | debug: true 76 | script: | 77 | mkdir abc/def 78 | ls -al 79 | 80 | - name: ssh key passphrase 81 | uses: ./ 82 | with: 83 | host: ${{ secrets.HOST }} 84 | username: ${{ secrets.USERNAME }} 85 | key: ${{ secrets.SSH2 }} 86 | port: ${{ secrets.PORT }} 87 | passphrase: ${{ secrets.PASSPHRASE }} 88 | script: | 89 | whoami 90 | ls -al 91 | 92 | - name: use insecure cipher 93 | uses: ./ 94 | with: 95 | host: ${{ secrets.HOST }} 96 | username: ${{ secrets.USERNAME }} 97 | password: ${{ secrets.PASSWORD }} 98 | port: ${{ secrets.PORT }} 99 | script: | 100 | ls \ 101 | -lah 102 | use_insecure_cipher: true 103 | 104 | # https://github.com/appleboy/ssh-action/issues/75#issuecomment-668314271 105 | - name: Multiline SSH commands interpreted as single lines 106 | uses: ./ 107 | with: 108 | host: ${{ secrets.HOST }} 109 | username: ${{ secrets.USERNAME }} 110 | password: ${{ secrets.PASSWORD }} 111 | port: ${{ secrets.PORT }} 112 | script_stop: true 113 | script: | 114 | ls \ 115 | -lah 116 | use_insecure_cipher: true 117 | 118 | # https://github.com/appleboy/ssh-action/issues/85 119 | - name: Deployment to multiple hosts with different ports 120 | uses: ./ 121 | with: 122 | host: "${{ secrets.HOST }}:${{ secrets.PORT }}" 123 | username: ${{ secrets.USERNAME }} 124 | password: ${{ secrets.PASSWORD }} 125 | port: 1024 126 | script_stop: true 127 | script: | 128 | ls \ 129 | -lah 130 | use_insecure_cipher: true 131 | 132 | # - name: SSH ED25519 Private Key 133 | # uses: ./ 134 | # with: 135 | # host: ${{ secrets.TUNNEL_HOST }} 136 | # username: ${{ secrets.TUNNEL_USERNAME }} 137 | # key: ${{ secrets.ID_ED25519 }} 138 | # port: ${{ secrets.TUNNEL_PORT }} 139 | # script: whoami 140 | 141 | testing02: 142 | name: testing with envs 143 | runs-on: ubuntu-latest 144 | steps: 145 | - name: checkout 146 | uses: actions/checkout@v1 147 | 148 | - name: pass environment 149 | uses: ./ 150 | env: 151 | FOO: "BAR" 152 | with: 153 | host: ${{ secrets.HOST }} 154 | username: ${{ secrets.USERNAME }} 155 | key: ${{ secrets.KEY }} 156 | port: ${{ secrets.PORT }} 157 | envs: FOO 158 | script: | 159 | echo "I am $FOO, thanks" 160 | echo "I am $BAR, thanks" 161 | 162 | - name: pass multiple environment 163 | uses: ./ 164 | env: 165 | FOO: "BAR" 166 | BAR: "FOO" 167 | SHA: ${{ github.sha }} 168 | PORT: ${{ secrets.PORT }} 169 | with: 170 | host: ${{ secrets.HOST }} 171 | username: ${{ secrets.USERNAME }} 172 | key: ${{ secrets.KEY }} 173 | port: ${{ secrets.PORT }} 174 | envs: FOO,BAR,SHA,PORT 175 | script: | 176 | echo "I am $FOO, thanks" 177 | echo "I am $BAR, thanks" 178 | echo "sha: $SHA" 179 | echo "port: $PORT" 180 | sh test.sh 181 | 182 | - name: custom envs format 183 | uses: ./ 184 | env: 185 | FOO: "BAR" 186 | AAA: "BBB" 187 | with: 188 | host: ${{ secrets.HOST }} 189 | username: ${{ secrets.USERNAME }} 190 | key: ${{ secrets.KEY }} 191 | port: ${{ secrets.PORT }} 192 | envs: FOO,BAR,AAA 193 | envs_format: export TEST_{NAME}={VALUE} 194 | script: | 195 | echo "I am $TEST_FOO, thanks" 196 | echo "I am $TEST_BAR, thanks" 197 | echo "I am $BAR, thanks" 198 | echo "I am $TEST_AAA, thanks" 199 | 200 | - name: pass all ENV variables to script 201 | uses: ./ 202 | env: 203 | INPUT_FOO: "BAR" 204 | INPUT_AAA: "BBB" 205 | with: 206 | host: ${{ secrets.HOST }} 207 | username: ${{ secrets.USERNAME }} 208 | key: ${{ secrets.KEY }} 209 | port: ${{ secrets.PORT }} 210 | allenvs: true 211 | script: | 212 | echo "I am $INPUT_FOO, thanks" 213 | echo "I am $INPUT_AAA, thanks" 214 | echo "$GITHUB_BASE_REF" 215 | echo "$GITHUB_REF" 216 | 217 | testing03: 218 | name: git clone and pull 219 | runs-on: ubuntu-latest 220 | steps: 221 | - name: checkout 222 | uses: actions/checkout@v1 223 | 224 | - name: clone private repository 225 | uses: ./ 226 | with: 227 | host: ${{ secrets.HOST }} 228 | username: ${{ secrets.USERNAME }} 229 | key: ${{ secrets.KEY }} 230 | port: ${{ secrets.PORT }} 231 | script_stop: true 232 | script: | 233 | git clone https://appleboy:${{ secrets.TEST_TOKEN }}@github.com/go-training/self-runner.git test_repository 234 | rm -rf test_repository 235 | 236 | testing04: 237 | name: docker login and pull 238 | runs-on: ubuntu-latest 239 | steps: 240 | - name: checkout 241 | uses: actions/checkout@v1 242 | 243 | - name: login GitHub Container Registry 244 | uses: ./ 245 | with: 246 | host: ${{ secrets.HOST }} 247 | username: ${{ secrets.USERNAME }} 248 | key: ${{ secrets.KEY }} 249 | port: ${{ secrets.PORT }} 250 | script_stop: true 251 | script: | 252 | echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u github.actor --password-stdin 253 | 254 | - name: login DockerHub Container Registry 255 | uses: ./ 256 | with: 257 | host: ${{ secrets.HOST }} 258 | username: ${{ secrets.USERNAME }} 259 | key: ${{ secrets.KEY }} 260 | port: ${{ secrets.PORT }} 261 | script_stop: true 262 | script: | 263 | echo ${{ secrets.DOCKERHUB_TOKEN }} | docker login -u ${{ secrets.DOCKERHUB_USERNAME }} --password-stdin 264 | 265 | testing05: 266 | name: sudo command 267 | runs-on: ubuntu-latest 268 | steps: 269 | - name: checkout 270 | uses: actions/checkout@v1 271 | 272 | - name: sudo command 273 | uses: ./ 274 | with: 275 | host: ${{ secrets.HOST }} 276 | username: ${{ secrets.USERNAME }} 277 | key: ${{ secrets.KEY }} 278 | port: ${{ secrets.PORT }} 279 | script_stop: true 280 | script: | 281 | sudo whoami 282 | -------------------------------------------------------------------------------- /README.zh-tw.md: -------------------------------------------------------------------------------- 1 | # 🚀 用於 GitHub Actions 的 SSH 2 | 3 | [GitHub Action](https://github.com/features/actions) for executing remote ssh commands. 4 | 5 | ![ssh workflow](./images/ssh-workflow.png) 6 | 7 | [![Actions Status](https://github.com/appleboy/ssh-action/workflows/remote%20ssh%20command/badge.svg)](https://github.com/appleboy/ssh-action/actions) 8 | 9 | **注意**: 只支援在 **Linux** [docker](https://www.docker.com/) 容器上執行。 10 | 11 | ## 輸入變數 12 | 13 | 更詳細的資訊,請參閱 [action.yml](./action.yml)。 14 | 15 | * `host` - SSH 主機 16 | * `port` - SSH 連接埠,預設為 `22` 17 | * `username` - SSH 使用者名稱 18 | * `password` - SSH 密碼 19 | * `passphrase` - 通常用於加密私鑰的 passphrase 20 | * `sync` - 同步執行多個主機上的命令,預設為 false 21 | * `timeout` - SSH 連接到遠端主機的超時時間,預設為 `30s` 22 | * `command_timeout` - SSH 命令超時時間,預設為 10m 23 | * `key` - SSH 私鑰的內容,例如 ~/.ssh/id_rsa 的原始內容,請記得包含 BEGIN 和 END 行 24 | * `key_path` - SSH 私鑰的路徑 25 | * `fingerprint` - 主機公鑰的 SHA256 指紋,預設為略過驗證 26 | * `script` - 執行命令 27 | * `script_stop` - 當出現第一個錯誤時停止執行命令 28 | * `envs` - 傳遞環境變數到 shell script 29 | * `debug` - 啟用偵錯模式 30 | * `use_insecure_cipher` - 使用不安全的密碼(ciphers)進行加密,參見 [#56](https://github.com/appleboy/ssh-action/issues/56) 31 | * `cipher` - 允許使用的密碼(ciphers)演算法。如果未指定,則使用適當的演算法 32 | 33 | SSH 代理設置: 34 | 35 | * `proxy_host` - 代理主機 36 | * `proxy_port` - 代理端口,預設為 `22` 37 | * `proxy_username` - 代理使用者名稱 38 | * `proxy_password` - 代理密碼 39 | * `proxy_passphrase` - 密碼通常用於加密私有金鑰 40 | * `proxy_timeout` - SSH 連線至代理主機的逾時時間,預設為 `30s` 41 | * `proxy_key` - SSH 代理私有金鑰內容 42 | * `proxy_key_path` - SSH 代理私有金鑰路徑 43 | * `proxy_fingerprint` - 代理主機公鑰的 SHA256 指紋,預設為跳過驗證 44 | * `proxy_use_insecure_cipher` - 使用不安全的加密方式,請參閱 [#56](https://github.com/appleboy/ssh-action/issues/56) 45 | * `proxy_cipher` - 允許的加密算法。如果未指定,則使用合理的算法 46 | 47 | ## 使用方式 48 | 49 | 執行遠端 SSH 命令 50 | 51 | ```yaml 52 | name: remote ssh command 53 | on: [push] 54 | jobs: 55 | 56 | build: 57 | name: Build 58 | runs-on: ubuntu-latest 59 | steps: 60 | - name: executing remote ssh commands using password 61 | uses: appleboy/ssh-action@v0.1.10 62 | with: 63 | host: ${{ secrets.HOST }} 64 | username: ${{ secrets.USERNAME }} 65 | password: ${{ secrets.PASSWORD }} 66 | port: ${{ secrets.PORT }} 67 | script: whoami 68 | ``` 69 | 70 | 畫面輸出 71 | 72 | ```sh 73 | ======CMD====== 74 | whoami 75 | ======END====== 76 | out: *** 77 | ============================================== 78 | ✅ Successfully executed commands to all host. 79 | ============================================== 80 | ``` 81 | 82 | ### 設置 SSH 金鑰 83 | 84 | 請在創建 SSH 金鑰並使用 SSH 金鑰時遵循以下步驟。最佳做法是在本地機器上創建 SSH 金鑰而不是遠端機器上。請使用 Github Secrets 中指定的用戶名登錄。生成 RSA 金鑰: 85 | 86 | ### 生成 RSA 金鑰 87 | 88 | ```bash 89 | ssh-keygen -t rsa -b 4096 -C "your_email@example.com" 90 | ``` 91 | 92 | ### 生成 ed25519 金鑰 93 | 94 | ```bash 95 | ssh-keygen -t ed25519 -a 200 -C "your_email@example.com" 96 | ``` 97 | 98 | 將新生成的金鑰添加到已授權的金鑰中。詳細了解已授權的金鑰請點擊[此處](https://www.ssh.com/ssh/authorized_keys/). 99 | 100 | ### 將 RSA 金鑰添加到已授權金鑰中 101 | 102 | ```bash 103 | cat .ssh/id_rsa.pub | ssh b@B 'cat >> .ssh/authorized_keys' 104 | ``` 105 | 106 | ### 將 ed25519 金鑰添加到已授權金鑰中 107 | 108 | ```bash 109 | cat .ssh/id_ed25519.pub | ssh b@B 'cat >> .ssh/authorized_keys' 110 | ``` 111 | 112 | 複製私鑰內容,然後將其粘貼到 Github Secrets 中。 113 | 114 | ### 複製 rsa 私鑰內容 115 | 116 | ```bash 117 | clip < ~/.ssh/id_rsa 118 | ``` 119 | 120 | ### 複製 ed25519 私鑰內容 121 | 122 | ```bash 123 | clip < ~/.ssh/id_ed25519 124 | ``` 125 | 126 | 有關無需密碼登錄 SSH 的詳細信息,請[參見該網站](http://www.linuxproblem.org/art_9.html)。 127 | 128 | **來自讀者的注意事項**: 根據您的 SSH 版本,您可能還需要進行以下更改: 129 | 130 | * 將公鑰放在 `.ssh/authorized_keys2` 中 131 | * 將 `.ssh` 的權限更改為700 132 | * 將 `.ssh/authorized_keys2` 的權限更改為640 133 | 134 | ### 如果你使用的是 OpenSSH 135 | 136 | 如果您正在使用 OpenSSH,並出現以下錯誤: 137 | 138 | ```bash 139 | ssh: handshake failed: ssh: unable to authenticate, attempted methods [none publickey] 140 | ``` 141 | 142 | 請確保您所選擇的密鑰演算法得到支援。在 Ubuntu 20.04 或更高版本上,您必須明確允許使用 SSH-RSA 演算法。請在 OpenSSH 守護進程文件中添加以下行(它可以是 `/etc/ssh/sshd_config` 或 `/etc/ssh/sshd_config.d/` 中的一個附著文件): 143 | 144 | ```bash 145 | CASignatureAlgorithms +ssh-rsa 146 | ``` 147 | 148 | 或者,`Ed25519` 密鑰在 OpenSSH 中默認被接受。如果需要,您可以使用它來替代 RSA。 149 | 150 | ```bash 151 | ssh-keygen -t ed25519 -a 200 -C "your_email@example.com" 152 | ``` 153 | 154 | ### Example 155 | 156 | #### 使用密碼執行遠端 SSH 命令 157 | 158 | ```yaml 159 | - name: executing remote ssh commands using password 160 | uses: appleboy/ssh-action@v0.1.10 161 | with: 162 | host: ${{ secrets.HOST }} 163 | username: ${{ secrets.USERNAME }} 164 | password: ${{ secrets.PASSWORD }} 165 | port: ${{ secrets.PORT }} 166 | script: whoami 167 | ``` 168 | 169 | #### 使用私鑰 170 | 171 | ```yaml 172 | - name: executing remote ssh commands using ssh key 173 | uses: appleboy/ssh-action@v0.1.10 174 | with: 175 | host: ${{ secrets.HOST }} 176 | username: ${{ secrets.USERNAME }} 177 | key: ${{ secrets.KEY }} 178 | port: ${{ secrets.PORT }} 179 | script: whoami 180 | ``` 181 | 182 | #### 多個命令 183 | 184 | ```yaml 185 | - name: multiple command 186 | uses: appleboy/ssh-action@v0.1.10 187 | with: 188 | host: ${{ secrets.HOST }} 189 | username: ${{ secrets.USERNAME }} 190 | key: ${{ secrets.KEY }} 191 | port: ${{ secrets.PORT }} 192 | script: | 193 | whoami 194 | ls -al 195 | ``` 196 | 197 | ![result](./images/output-result.png) 198 | 199 | #### 多台主機 200 | 201 | ```diff 202 | - name: multiple host 203 | uses: appleboy/ssh-action@v0.1.10 204 | with: 205 | - host: "foo.com" 206 | + host: "foo.com,bar.com" 207 | username: ${{ secrets.USERNAME }} 208 | key: ${{ secrets.KEY }} 209 | port: ${{ secrets.PORT }} 210 | script: | 211 | whoami 212 | ls -al 213 | ``` 214 | 215 | #### 多個不同端口的主機 216 | 217 | ```diff 218 | - name: multiple host 219 | uses: appleboy/ssh-action@v0.1.10 220 | with: 221 | - host: "foo.com" 222 | + host: "foo.com:1234,bar.com:5678" 223 | username: ${{ secrets.USERNAME }} 224 | key: ${{ secrets.KEY }} 225 | script: | 226 | whoami 227 | ls -al 228 | ``` 229 | 230 | #### 在多個主機上同步執行 231 | 232 | ```diff 233 | - name: multiple host 234 | uses: appleboy/ssh-action@v0.1.10 235 | with: 236 | host: "foo.com,bar.com" 237 | + sync: true 238 | username: ${{ secrets.USERNAME }} 239 | key: ${{ secrets.KEY }} 240 | port: ${{ secrets.PORT }} 241 | script: | 242 | whoami 243 | ls -al 244 | ``` 245 | 246 | #### 將環境變量傳遞到 Shell 腳本 247 | 248 | ```diff 249 | - name: pass environment 250 | uses: appleboy/ssh-action@v0.1.10 251 | + env: 252 | + FOO: "BAR" 253 | + BAR: "FOO" 254 | + SHA: ${{ github.sha }} 255 | with: 256 | host: ${{ secrets.HOST }} 257 | username: ${{ secrets.USERNAME }} 258 | key: ${{ secrets.KEY }} 259 | port: ${{ secrets.PORT }} 260 | + envs: FOO,BAR,SHA 261 | script: | 262 | echo "I am $FOO" 263 | echo "I am $BAR" 264 | echo "sha: $SHA" 265 | ``` 266 | 267 | _在 `env` 對象中,您需要將每個環境變量作為字符串傳遞,傳遞 `Integer` 數據類型或任何其他類型可能會產生意外結果。_ 268 | 269 | #### 在第一次失敗後停止腳本 270 | 271 | > ex: missing `abc` folder 272 | 273 | ```diff 274 | - name: stop script if command error 275 | uses: appleboy/ssh-action@v0.1.10 276 | with: 277 | host: ${{ secrets.HOST }} 278 | username: ${{ secrets.USERNAME }} 279 | key: ${{ secrets.KEY }} 280 | port: ${{ secrets.PORT }} 281 | + script_stop: true 282 | script: | 283 | mkdir abc/def 284 | ls -al 285 | ``` 286 | 287 | 畫面輸出: 288 | 289 | ```sh 290 | ======CMD====== 291 | mkdir abc/def 292 | ls -al 293 | 294 | ======END====== 295 | 2019/11/21 01:16:21 Process exited with status 1 296 | err: mkdir: cannot create directory ‘abc/def’: No such file or directory 297 | ##[error]Docker run failed with exit code 1 298 | ``` 299 | 300 | #### 如何使用 `ProxyCommand` 連接遠程服務器? 301 | 302 | ```bash 303 | +--------+ +----------+ +-----------+ 304 | | Laptop | <--> | Jumphost | <--> | FooServer | 305 | +--------+ +----------+ +-----------+ 306 | ``` 307 | 308 | 在您的 `~/.ssh/config` 文件中,您會看到以下內容。 309 | 310 | ```bash 311 | Host Jumphost 312 | HostName Jumphost 313 | User ubuntu 314 | Port 22 315 | IdentityFile ~/.ssh/keys/jump_host.pem 316 | 317 | Host FooServer 318 | HostName FooServer 319 | User ubuntu 320 | Port 22 321 | ProxyCommand ssh -q -W %h:%p Jumphost 322 | ``` 323 | 324 | #### 如何將其轉換為 GitHubActions 的 YAML 格式? 325 | 326 | ```diff 327 | - name: ssh proxy command 328 | uses: appleboy/ssh-action@v0.1.10 329 | with: 330 | host: ${{ secrets.HOST }} 331 | username: ${{ secrets.USERNAME }} 332 | key: ${{ secrets.KEY }} 333 | port: ${{ secrets.PORT }} 334 | + proxy_host: ${{ secrets.PROXY_HOST }} 335 | + proxy_username: ${{ secrets.PROXY_USERNAME }} 336 | + proxy_key: ${{ secrets.PROXY_KEY }} 337 | + proxy_port: ${{ secrets.PROXY_PORT }} 338 | script: | 339 | mkdir abc/def 340 | ls -al 341 | ``` 342 | 343 | #### 如何保護私鑰? 344 | 345 | 密碼短語通常用於加密私鑰。這使得攻擊者無法單獨使用密鑰文件。文件泄露可能來自備份或停用的硬件,黑客通常可以從受攻擊系統中洩露文件。因此,保護私鑰非常重要。 346 | 347 | ```diff 348 | - name: ssh key passphrase 349 | uses: appleboy/ssh-action@v0.1.10 350 | with: 351 | host: ${{ secrets.HOST }} 352 | username: ${{ secrets.USERNAME }} 353 | key: ${{ secrets.KEY }} 354 | port: ${{ secrets.PORT }} 355 | + passphrase: ${{ secrets.PASSPHRASE }} 356 | script: | 357 | whoami 358 | ls -al 359 | ``` 360 | 361 | #### 使用主機指紋驗證 362 | 363 | 設置 SSH 主機指紋驗證可以幫助防止中間人攻擊。在設置之前,運行以下命令以獲取 SSH 主機指紋。請記得將 `ed25519` 替換為您的適當金鑰類型(`rsa`、 `dsa`等),而 `example.com` 則替換為您的主機。 364 | 365 | 現代 OpenSSH 版本中,需要提取的_默認金鑰_類型是 `rsa`(從版本 5.1 開始)、`ecdsa`(從版本 6.0 開始)和 `ed25519`(從版本 6.7 開始)。 366 | 367 | ```sh 368 | ssh example.com ssh-keygen -l -f /etc/ssh/ssh_host_ed25519_key.pub | cut -d ' ' -f2 369 | ``` 370 | 371 | 現在您可以調整您的配置: 372 | 373 | ```diff 374 | - name: ssh key passphrase 375 | uses: appleboy/ssh-action@v0.1.10 376 | with: 377 | host: ${{ secrets.HOST }} 378 | username: ${{ secrets.USERNAME }} 379 | key: ${{ secrets.KEY }} 380 | port: ${{ secrets.PORT }} 381 | + fingerprint: ${{ secrets.FINGERPRINT }} 382 | script: | 383 | whoami 384 | ls -al 385 | ``` 386 | 387 | ## 貢獻 388 | 389 | 我們非常希望您為 `appleboy/ssh-action` 做出貢獻,歡迎提交請求! 390 | 391 | ## 授權方式 392 | 393 | 本項目中的腳本和文檔采用 [MIT](LICENSE) 許可證 發布。 394 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🚀 SSH for GitHub Actions 2 | 3 | [繁體中文](./README.zh-tw.md) 4 | 5 | [GitHub Action](https://github.com/features/actions) for executing remote ssh commands. 6 | 7 | ![ssh workflow](./images/ssh-workflow.png) 8 | 9 | [![Actions Status](https://github.com/appleboy/ssh-action/workflows/remote%20ssh%20command/badge.svg)](https://github.com/appleboy/ssh-action/actions) 10 | 11 | **Important**: Only support **Linux** [docker](https://www.docker.com/) container. 12 | 13 | This thing is built using [Golang](https://go.dev) and [drone-ssh](https://github.com/appleboy/drone-ssh). 🚀 14 | 15 | ## Input variables 16 | 17 | See [action.yml](./action.yml) for more detailed information. 18 | 19 | | Input Parameter | Description | Default Value | 20 | |-------------------------|-----------------------------------------------------------------|---------------| 21 | | host | SSH host address | | 22 | | port | SSH port number | 22 | 23 | | passphrase | SSH key passphrase | | 24 | | username | SSH username | | 25 | | password | SSH password | | 26 | | sync | Enable synchronous execution if multiple hosts | false | 27 | | use_insecure_cipher | Include more ciphers with use_insecure_cipher | false | 28 | | cipher | Allowed cipher algorithms. If unspecified, a sensible default | | 29 | | timeout | Timeout duration for SSH to host | 30s | 30 | | command_timeout | Timeout duration for SSH command | 10m | 31 | | key | Content of SSH private key. e.g., raw content of ~/.ssh/id_rsa | | 32 | | key_path | Path of SSH private key | | 33 | | fingerprint | SHA256 fingerprint of the host public key | | 34 | | proxy_host | SSH proxy host | | 35 | | proxy_port | SSH proxy port | 22 | 36 | | proxy_username | SSH proxy username | | 37 | | proxy_password | SSH proxy password | | 38 | | proxy_passphrase | SSH proxy key passphrase | | 39 | | proxy_timeout | Timeout for SSH to proxy host | 30s | 40 | | proxy_key | Content of SSH proxy private key | | 41 | | proxy_key_path | Path of SSH proxy private key | | 42 | | proxy_fingerprint | SHA256 fingerprint of the proxy host public key | | 43 | | proxy_cipher | Allowed cipher algorithms for the proxy | | 44 | | proxy_use_insecure_cipher | Include more ciphers with use_insecure_cipher for the proxy | false | 45 | | script | Execute commands | | 46 | | script_stop | Stop script after first failure | false | 47 | | envs | Pass environment variables to shell script | | 48 | | envs_format | Flexible configuration of environment value transfer | | 49 | | debug | Enable debug mode | false | 50 | | allenvs | Pass all environment variables to shell script | false | 51 | 52 | ## Usage 53 | 54 | Executing remote ssh commands. 55 | 56 | ```yaml 57 | name: remote ssh command 58 | on: [push] 59 | jobs: 60 | 61 | build: 62 | name: Build 63 | runs-on: ubuntu-latest 64 | steps: 65 | - name: executing remote ssh commands using password 66 | uses: appleboy/ssh-action@v1.0.0 67 | with: 68 | host: ${{ secrets.HOST }} 69 | username: ${{ secrets.USERNAME }} 70 | password: ${{ secrets.PASSWORD }} 71 | port: ${{ secrets.PORT }} 72 | script: whoami 73 | ``` 74 | 75 | output: 76 | 77 | ```sh 78 | ======CMD====== 79 | whoami 80 | ======END====== 81 | out: *** 82 | ============================================== 83 | ✅ Successfully executed commands to all host. 84 | ============================================== 85 | ``` 86 | 87 | ### Setting up a SSH Key 88 | 89 | Make sure to follow the below steps while creating SSH Keys and using them. 90 | The best practice is create the SSH Keys on local machine not remote machine. 91 | Login with username specified in Github Secrets. Generate a RSA Key-Pair: 92 | 93 | ### Generate rsa key 94 | 95 | ```bash 96 | ssh-keygen -t rsa -b 4096 -C "your_email@example.com" 97 | ``` 98 | 99 | ### Generate ed25519 key 100 | 101 | ```bash 102 | ssh-keygen -t ed25519 -a 200 -C "your_email@example.com" 103 | ``` 104 | 105 | Add newly generated key into Authorized keys. Read more about authorized keys [here](https://www.ssh.com/ssh/authorized_keys/). 106 | 107 | ### Add rsa key into Authorized keys 108 | 109 | ```bash 110 | cat .ssh/id_rsa.pub | ssh b@B 'cat >> .ssh/authorized_keys' 111 | ``` 112 | 113 | ### Add ed25519 key into Authorized keys 114 | 115 | ```bash 116 | cat .ssh/id_ed25519.pub | ssh b@B 'cat >> .ssh/authorized_keys' 117 | ``` 118 | 119 | Copy Private Key content and paste in Github Secrets. 120 | 121 | ### Copy rsa Private key 122 | 123 | ```bash 124 | clip < ~/.ssh/id_rsa 125 | ``` 126 | 127 | ### Copy ed25519 Private key 128 | 129 | ```bash 130 | clip < ~/.ssh/id_ed25519 131 | ``` 132 | 133 | See the detail information about [SSH login without password](http://www.linuxproblem.org/art_9.html). 134 | 135 | **A note** from one of our readers: Depending on your version of SSH you might also have to do the following changes: 136 | 137 | * Put the public key in `.ssh/authorized_keys2` 138 | * Change the permissions of `.ssh` to 700 139 | * Change the permissions of `.ssh/authorized_keys2` to 640 140 | 141 | ### If you are using OpenSSH 142 | 143 | If you are currently using OpenSSH and are getting the following error: 144 | 145 | ```bash 146 | ssh: handshake failed: ssh: unable to authenticate, attempted methods [none publickey] 147 | ``` 148 | 149 | Make sure that your key algorithm of choice is supported. On Ubuntu 20.04 or later you must explicitly allow the use of the ssh-rsa algorithm. Add the following line to your OpenSSH daemon file (which is either `/etc/ssh/sshd_config` or a drop-in file under `/etc/ssh/sshd_config.d/`): 150 | 151 | ```bash 152 | CASignatureAlgorithms +ssh-rsa 153 | ``` 154 | 155 | Alternatively, `ed25519` keys are accepted by default in OpenSSH. You could use this instead of rsa if needed: 156 | 157 | ```bash 158 | ssh-keygen -t ed25519 -a 200 -C "your_email@example.com" 159 | ``` 160 | 161 | ### Example 162 | 163 | #### Executing remote ssh commands using password 164 | 165 | ```yaml 166 | - name: executing remote ssh commands using password 167 | uses: appleboy/ssh-action@v1.0.0 168 | with: 169 | host: ${{ secrets.HOST }} 170 | username: ${{ secrets.USERNAME }} 171 | password: ${{ secrets.PASSWORD }} 172 | port: ${{ secrets.PORT }} 173 | script: whoami 174 | ``` 175 | 176 | #### Using private key 177 | 178 | ```yaml 179 | - name: executing remote ssh commands using ssh key 180 | uses: appleboy/ssh-action@v1.0.0 181 | with: 182 | host: ${{ secrets.HOST }} 183 | username: ${{ secrets.USERNAME }} 184 | key: ${{ secrets.KEY }} 185 | port: ${{ secrets.PORT }} 186 | script: whoami 187 | ``` 188 | 189 | #### Multiple Commands 190 | 191 | ```yaml 192 | - name: multiple command 193 | uses: appleboy/ssh-action@v1.0.0 194 | with: 195 | host: ${{ secrets.HOST }} 196 | username: ${{ secrets.USERNAME }} 197 | key: ${{ secrets.KEY }} 198 | port: ${{ secrets.PORT }} 199 | script: | 200 | whoami 201 | ls -al 202 | ``` 203 | 204 | ![result](./images/output-result.png) 205 | 206 | #### Multiple Hosts 207 | 208 | ```diff 209 | - name: multiple host 210 | uses: appleboy/ssh-action@v1.0.0 211 | with: 212 | - host: "foo.com" 213 | + host: "foo.com,bar.com" 214 | username: ${{ secrets.USERNAME }} 215 | key: ${{ secrets.KEY }} 216 | port: ${{ secrets.PORT }} 217 | script: | 218 | whoami 219 | ls -al 220 | ``` 221 | 222 | #### Multiple hosts with different port 223 | 224 | ```diff 225 | - name: multiple host 226 | uses: appleboy/ssh-action@v1.0.0 227 | with: 228 | - host: "foo.com" 229 | + host: "foo.com:1234,bar.com:5678" 230 | username: ${{ secrets.USERNAME }} 231 | key: ${{ secrets.KEY }} 232 | script: | 233 | whoami 234 | ls -al 235 | ``` 236 | 237 | #### Synchronous execution on multiple hosts 238 | 239 | ```diff 240 | - name: multiple host 241 | uses: appleboy/ssh-action@v1.0.0 242 | with: 243 | host: "foo.com,bar.com" 244 | + sync: true 245 | username: ${{ secrets.USERNAME }} 246 | key: ${{ secrets.KEY }} 247 | port: ${{ secrets.PORT }} 248 | script: | 249 | whoami 250 | ls -al 251 | ``` 252 | 253 | #### Pass environment variable to shell script 254 | 255 | ```diff 256 | - name: pass environment 257 | uses: appleboy/ssh-action@v1.0.0 258 | + env: 259 | + FOO: "BAR" 260 | + BAR: "FOO" 261 | + SHA: ${{ github.sha }} 262 | with: 263 | host: ${{ secrets.HOST }} 264 | username: ${{ secrets.USERNAME }} 265 | key: ${{ secrets.KEY }} 266 | port: ${{ secrets.PORT }} 267 | + envs: FOO,BAR,SHA 268 | script: | 269 | echo "I am $FOO" 270 | echo "I am $BAR" 271 | echo "sha: $SHA" 272 | ``` 273 | 274 | _Inside `env` object, you need to pass every environment variable as a string, passing `Integer` data type or any other may output unexpected results._ 275 | 276 | #### Stop script after first failure 277 | 278 | > ex: missing `abc` folder 279 | 280 | ```diff 281 | - name: stop script if command error 282 | uses: appleboy/ssh-action@v1.0.0 283 | with: 284 | host: ${{ secrets.HOST }} 285 | username: ${{ secrets.USERNAME }} 286 | key: ${{ secrets.KEY }} 287 | port: ${{ secrets.PORT }} 288 | + script_stop: true 289 | script: | 290 | mkdir abc/def 291 | ls -al 292 | ``` 293 | 294 | output: 295 | 296 | ```sh 297 | ======CMD====== 298 | mkdir abc/def 299 | ls -al 300 | 301 | ======END====== 302 | 2019/11/21 01:16:21 Process exited with status 1 303 | err: mkdir: cannot create directory ‘abc/def’: No such file or directory 304 | ##[error]Docker run failed with exit code 1 305 | ``` 306 | 307 | #### How to connect remote server using `ProxyCommand`? 308 | 309 | ```bash 310 | +--------+ +----------+ +-----------+ 311 | | Laptop | <--> | Jumphost | <--> | FooServer | 312 | +--------+ +----------+ +-----------+ 313 | ``` 314 | 315 | in your `~/.ssh/config`, you will see the following. 316 | 317 | ```bash 318 | Host Jumphost 319 | HostName Jumphost 320 | User ubuntu 321 | Port 22 322 | IdentityFile ~/.ssh/keys/jump_host.pem 323 | 324 | Host FooServer 325 | HostName FooServer 326 | User ubuntu 327 | Port 22 328 | ProxyCommand ssh -q -W %h:%p Jumphost 329 | ``` 330 | 331 | #### How to convert to YAML format of GitHubActions 332 | 333 | ```diff 334 | - name: ssh proxy command 335 | uses: appleboy/ssh-action@v1.0.0 336 | with: 337 | host: ${{ secrets.HOST }} 338 | username: ${{ secrets.USERNAME }} 339 | key: ${{ secrets.KEY }} 340 | port: ${{ secrets.PORT }} 341 | + proxy_host: ${{ secrets.PROXY_HOST }} 342 | + proxy_username: ${{ secrets.PROXY_USERNAME }} 343 | + proxy_key: ${{ secrets.PROXY_KEY }} 344 | + proxy_port: ${{ secrets.PROXY_PORT }} 345 | script: | 346 | mkdir abc/def 347 | ls -al 348 | ``` 349 | 350 | #### Protecting a Private Key 351 | 352 | The purpose of the passphrase is usually to encrypt the private key. 353 | This makes the key file by itself useless to an attacker. 354 | It is not uncommon for files to leak from backups or decommissioned hardware, and hackers commonly exfiltrate files from compromised systems. 355 | 356 | ```diff 357 | - name: ssh key passphrase 358 | uses: appleboy/ssh-action@v1.0.0 359 | with: 360 | host: ${{ secrets.HOST }} 361 | username: ${{ secrets.USERNAME }} 362 | key: ${{ secrets.KEY }} 363 | port: ${{ secrets.PORT }} 364 | + passphrase: ${{ secrets.PASSPHRASE }} 365 | script: | 366 | whoami 367 | ls -al 368 | ``` 369 | 370 | #### Using host fingerprint verification 371 | 372 | Setting up SSH host fingerprint verification can help to prevent Person-in-the-Middle attacks. Before setting this up, run the command below to get your SSH host fingerprint. Remember to replace `ed25519` with your appropriate key type (`rsa`, `dsa`, etc.) that your server is using and `example.com` with your host. 373 | 374 | In modern OpenSSH releases, the _default_ key types to be fetched are `rsa` (since version 5.1), `ecdsa` (since version 6.0), and `ed25519` (since version 6.7). 375 | 376 | ```sh 377 | ssh example.com ssh-keygen -l -f /etc/ssh/ssh_host_ed25519_key.pub | cut -d ' ' -f2 378 | ``` 379 | 380 | Now you can adjust you config: 381 | 382 | ```diff 383 | - name: ssh key passphrase 384 | uses: appleboy/ssh-action@v1.0.0 385 | with: 386 | host: ${{ secrets.HOST }} 387 | username: ${{ secrets.USERNAME }} 388 | key: ${{ secrets.KEY }} 389 | port: ${{ secrets.PORT }} 390 | + fingerprint: ${{ secrets.FINGERPRINT }} 391 | script: | 392 | whoami 393 | ls -al 394 | ``` 395 | 396 | ## Contributing 397 | 398 | We would love for you to contribute to `appleboy/ssh-action`, pull requests are welcome! 399 | 400 | ## License 401 | 402 | The scripts and documentation in this project are released under the [MIT License](LICENSE) 403 | --------------------------------------------------------------------------------