Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 16 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Setup ssh session
uses: Warpbuilds/action-debugger@v1.3
uses: Warpbuilds/action-debugger@v2
```

To get the connection string, just open the `Checks` tab in your Pull Request or Run and scroll to the bottom. There you can connect either directly per SSH or via a web based terminal. The connection string is also logged inside workflow logs.
To get the connection string, just open the `Checks` tab in your Pull Request or Run and scroll to the bottom. There you can connect directly per SSH, or via a web based terminal when `limit-access-to-actor` is `false`. The connection string is also logged inside workflow logs.

![screenshots](https://github.com/WarpBuilds/action-debugger/assets/9110203/2e1ce772-285f-4a4e-a41a-d12054d8960e)
![check](https://github.com/WarpBuilds/action-debugger/assets/9110203/d8cb31ef-5044-4f39-a391-a198ec09ce39)
Expand Down Expand Up @@ -66,7 +66,7 @@ jobs:
steps:
# Enable ssh debugging of manually-triggered workflows if the input option was provided
- name: Setup interactive ssh session
uses: Warpbuilds/action-debugger@v1.3
uses: Warpbuilds/action-debugger@v2
if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }}
```

Expand All @@ -89,7 +89,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Setup interactive ssh session
uses: Warpbuilds/action-debugger@v1.3
uses: Warpbuilds/action-debugger@v2
with:
detached: true
```
Expand Down Expand Up @@ -119,7 +119,7 @@ jobs:
- uses: actions/checkout@v4
- name: Setup ssh session
id: debugger
uses: Warpbuilds/action-debugger@v1.3
uses: Warpbuilds/action-debugger@v2
with:
detached: true

Expand All @@ -145,7 +145,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Setup ssh session
uses: Warpbuilds/action-debugger@v1.3
uses: Warpbuilds/action-debugger@v2
with:
sudo: false
```
Expand All @@ -163,7 +163,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Setup interactive ssh session
uses: Warpbuilds/action-debugger@v1.3
uses: Warpbuilds/action-debugger@v2
timeout-minutes: 15
```

Expand All @@ -184,15 +184,17 @@ jobs:
- uses: actions/checkout@v4
- name: Setup interactive ssh session
if: ${{ failure() }}
uses: Warpbuilds/action-debugger@v1.3
uses: Warpbuilds/action-debugger@v2
```
<!--
{% endraw %}
-->

## Use registered public SSH key(s)

If [you have registered one or more public SSH keys with your GitHub profile](https://docs.github.com/en/github/authenticating-to-github/adding-a-new-ssh-key-to-your-github-account), ssh will be started such that only those keys are authorized to connect, otherwise anybody can connect to the ssh session. If you want to require a public SSH key to be installed with the ssh session, no matter whether the user who started the workflow has registered any in their GitHub profile, you will need to configure the setting `limit-access-to-actor` to `true`, like so:
By default, only the user who started the workflow can connect, using the public SSH keys [registered with their GitHub profile](https://docs.github.com/en/github/authenticating-to-github/adding-a-new-ssh-key-to-your-github-account). If that user has no public SSH key, the step fails and no session starts. The web shell is also turned off.

To allow anyone with the connection string to connect, set `limit-access-to-actor` to `false`. Do not do this on a public repository, because anyone who can read the job log can then open a shell on the runner.

```yaml
name: CI
Expand All @@ -203,9 +205,9 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Setup interactive ssh session
uses: Warpbuilds/action-debugger@v1.3
uses: Warpbuilds/action-debugger@v2
with:
limit-access-to-actor: true
limit-access-to-actor: false
```

If the registered public SSH key is not your default private SSH key, you will need to specify the path manually, like so: `ssh -i <path-to-key> <ssh-connection-string>`.
Expand All @@ -223,7 +225,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Setup ssh session
uses: Warpbuilds/action-debugger@v1.3
uses: Warpbuilds/action-debugger@v2
with:
tmate-server-host: ssh.tmate.io
tmate-server-port: 22
Expand All @@ -242,7 +244,7 @@ jobs:
build:
runs-on: [self-hosted, linux]
steps:
- uses: Warpbuilds/action-debugger@v1.3
- uses: Warpbuilds/action-debugger@v2
with:
install-dependencies: false
```
Expand All @@ -260,7 +262,7 @@ jobs:
steps:
- uses: msys2/setup-msys2@v2
id: setup-msys2
- uses: Warpbuilds/action-debugger@v1.3
- uses: Warpbuilds/action-debugger@v2
with:
msys2-location: ${{ steps.setup-msys2.outputs.msys2-location }}
```
Expand Down
4 changes: 2 additions & 2 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ inputs:
required: false
default: "true"
limit-access-to-actor:
description: "Whether to authorize only the public SSH keys of the user triggering the workflow (defaults to true if the GitHub profile of the user has a public SSH key)"
description: "Whether to authorize only the public SSH keys of the user triggering the workflow. The session fails to start if the user has no public SSH key. Set to false to allow anyone with the session address to connect."
required: false
default: "auto"
default: "true"
detached:
description: "In detached mode, the workflow job will continue while the ssh session is active"
required: false
Expand Down
4 changes: 2 additions & 2 deletions detached/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ inputs:
required: false
default: "true"
limit-access-to-actor:
description: "Whether to authorize only the public SSH keys of the user triggering the workflow (defaults to true if the GitHub profile of the user has a public SSH key)"
description: "Whether to authorize only the public SSH keys of the user triggering the workflow. The session fails to start if the user has no public SSH key. Set to false to allow anyone with the session address to connect."
required: false
default: "auto"
default: "true"
detached:
description: "In detached mode, the workflow job will continue while the ssh session is active"
required: false
Expand Down
20 changes: 5 additions & 15 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -35242,7 +35242,7 @@ function core_error(message, properties = {}) {
* @param properties optional properties to add to the annotation.
*/
function warning(message, properties = {}) {
command_issueCommand('warning', utils_toCommandProperties(properties), message instanceof Error ? message.toString() : message);
issueCommand('warning', toCommandProperties(properties), message instanceof Error ? message.toString() : message);
}
/**
* Adds a notice issue
Expand Down Expand Up @@ -41232,9 +41232,8 @@ async function run() {

let newSessionExtra = "";
let tmateSSHDashI = "";
let publicSSHKeysWarning = "";
const limitAccessToActor = getInput("limit-access-to-actor");
if (limitAccessToActor === "true" || limitAccessToActor === "auto") {
if (limitAccessToActor !== "false") {
const { actor, apiUrl } = github_context;
const auth = getInput("github-token");
const octokit = new dist_src_Octokit({
Expand All @@ -41246,12 +41245,9 @@ async function run() {
username: actor,
});
if (keys.data.length === 0) {
if (limitAccessToActor === "auto")
publicSSHKeysWarning = `No public SSH keys found for ${actor}; continuing without them even if it is less secure (please consider adding an SSH key, see https://docs.github.com/en/authentication/connecting-to-github-with-ssh/adding-a-new-ssh-key-to-your-github-account)`;
else
throw new Error(
`No public SSH keys registered with ${actor}'s GitHub profile`
);
throw new Error(
`No public SSH keys registered with ${actor}'s GitHub profile. Add one at https://github.com/settings/keys, or set limit-access-to-actor: false to allow anyone with the session address to connect.`
);
} else {
const sshPath = external_path_default().join(external_os_default().homedir(), ".ssh");
await external_fs_default().promises.mkdir(sshPath, { recursive: true });
Expand Down Expand Up @@ -41354,9 +41350,6 @@ async function run() {
core_debug("Entering detached mode");

let message = "";
if (publicSSHKeysWarning) {
message += `::warning::${publicSSHKeysWarning}\n`;
}
if (tmateWeb) {
message += `::notice::Web shell: ${tmateWeb}\n`;
}
Expand All @@ -41383,9 +41376,6 @@ async function run() {

core_debug("Entering main loop");
while (true) {
if (publicSSHKeysWarning) {
warning(publicSSHKeysWarning);
}
if (tmateWeb) {
info(`Web shell: ${tmateWeb}`);
}
Expand Down
18 changes: 4 additions & 14 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,8 @@ export async function run() {

let newSessionExtra = "";
let tmateSSHDashI = "";
let publicSSHKeysWarning = "";
const limitAccessToActor = core.getInput("limit-access-to-actor");
if (limitAccessToActor === "true" || limitAccessToActor === "auto") {
if (limitAccessToActor !== "false") {
const { actor, apiUrl } = github.context;
const auth = core.getInput("github-token");
const octokit = new Octokit({
Expand All @@ -169,12 +168,9 @@ export async function run() {
username: actor,
});
if (keys.data.length === 0) {
if (limitAccessToActor === "auto")
publicSSHKeysWarning = `No public SSH keys found for ${actor}; continuing without them even if it is less secure (please consider adding an SSH key, see https://docs.github.com/en/authentication/connecting-to-github-with-ssh/adding-a-new-ssh-key-to-your-github-account)`;
else
throw new Error(
`No public SSH keys registered with ${actor}'s GitHub profile`
);
throw new Error(
`No public SSH keys registered with ${actor}'s GitHub profile. Add one at https://github.com/settings/keys, or set limit-access-to-actor: false to allow anyone with the session address to connect.`
);
} else {
const sshPath = path.join(os.homedir(), ".ssh");
await fs.promises.mkdir(sshPath, { recursive: true });
Expand Down Expand Up @@ -277,9 +273,6 @@ export async function run() {
core.debug("Entering detached mode");

let message = "";
if (publicSSHKeysWarning) {
message += `::warning::${publicSSHKeysWarning}\n`;
}
if (tmateWeb) {
message += `::notice::Web shell: ${tmateWeb}\n`;
}
Expand All @@ -306,9 +299,6 @@ export async function run() {

core.debug("Entering main loop");
while (true) {
if (publicSSHKeysWarning) {
core.warning(publicSSHKeysWarning);
}
if (tmateWeb) {
core.info(`Web shell: ${tmateWeb}`);
}
Expand Down
45 changes: 43 additions & 2 deletions src/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,16 @@ jest.mock(
}),
{ virtual: true }
);
const mockListPublicKeysForUser = jest.fn(async () => ({
data: [{ key: "ssh-ed25519 AAAA test-user" }],
}));
jest.mock(
"@octokit/rest",
() => ({
__esModule: true,
Octokit: class {
users = {
listPublicKeysForUser: async () => ({ data: [] }),
listPublicKeysForUser: mockListPublicKeysForUser,
};
},
}),
Expand Down Expand Up @@ -124,7 +127,7 @@ describe("Tmate GitHub integration", () => {
Object.defineProperty(process, "platform", {
value: "win32",
});
core.getInput.mockReturnValueOnce("false");
core.getInput.mockReturnValueOnce("false").mockReturnValueOnce("false");
const customConnectionString = "foobar";
execShellCommand.mockReturnValue(Promise.resolve(customConnectionString));
await run();
Expand Down Expand Up @@ -244,6 +247,44 @@ describe("Tmate GitHub integration", () => {

expect(core.setFailed).not.toHaveBeenCalled();
});

it("should authorize only the actor's public SSH keys by default", async () => {
core.getInput.mockReturnValue("");

await run();

expect(core.setFailed).not.toHaveBeenCalled();
expect(execShellCommand).toHaveBeenCalledWith(
expect.stringContaining("-a \"")
);
});

it("should fail when the actor has no public SSH keys", async () => {
core.getInput.mockReturnValue("");
mockListPublicKeysForUser.mockResolvedValueOnce({ data: [] });

await run();

expect(core.setFailed).toHaveBeenCalledWith(
expect.objectContaining({
message: expect.stringContaining("No public SSH keys registered with test-user"),
})
);
expect(execShellCommand).not.toHaveBeenCalledWith(
expect.stringContaining("new-session")
);
});

it("should allow anyone to connect when limit-access-to-actor is false", async () => {
core.getInput.mockImplementation((name) =>
name === "limit-access-to-actor" ? "false" : ""
);

await run();

expect(core.setFailed).not.toHaveBeenCalled();
expect(mockListPublicKeysForUser).not.toHaveBeenCalled();
});
it("should validate correct tmate options", async () => {
// Check for the happy path first.
core.getInput.mockImplementation(function (opt) {
Expand Down
Loading