0
0
mirror of https://github.com/bpg/terraform-provider-proxmox.git synced 2025-06-30 02:31:10 +00:00

fix(file): use sudo for snippets upload (#1004)

* fix(file): use `sudo` for snippets upload

Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>

* fix: linter

Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>

* fix: no more rm -rf

Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>

---------

Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
This commit is contained in:
Pavel Boldyrev 2024-02-05 21:28:28 -05:00 committed by GitHub
parent 78fa7b1a4f
commit 60fb679e9f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 70 additions and 15 deletions

View File

@ -1,9 +1,11 @@
{
"git.alwaysSignOff": true,
"cSpell.words": [
"capi",
"CLRF",
"iothread",
"keyctl",
"nolint",
"proxmoxtf",
"qcow",
"rootfs",
@ -13,7 +15,8 @@
"virtio",
"VLANID",
"vmbr",
"VMID"
"VMID",
"vztmpl"
],
"go.lintTool": "golangci-lint",
"go.lintFlags": [

View File

@ -160,12 +160,12 @@ You can configure the `sudo` privilege for the user via the command line on the
sudo visudo
```
Add the following line to the end of the file:
Add the following lines to the end of the file:
```sh
terraform ALL=(root) NOPASSWD: /sbin/pvesm
terraform ALL=(root) NOPASSWD: /sbin/qm
terraform ALL=(root) NOPASSWD: /usr/bin/echo tfpve
terraform ALL=(root) NOPASSWD: /usr/bin/mv /tmp/tfpve/* /var/lib/vz/*
```
Save the file and exit.
@ -179,10 +179,10 @@ You can configure the `sudo` privilege for the user via the command line on the
- Test the SSH connection and password-less `sudo`:
```sh
ssh terraform@<target-node> sudo echo tfpve
ssh terraform@<target-node> sudo pvesm apiinfo
```
You should be able to connect to the target node and see the output `tfpve` on the screen without being prompted for your password.
You should be able to connect to the target node and see the output containing `APIVER <number>` on the screen without being prompted for your password.
### Node IP address used for SSH connection

View File

@ -17,10 +17,12 @@ import (
"net/url"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"time"
"github.com/google/uuid"
"github.com/hashicorp/go-cty/cty"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
@ -538,7 +540,12 @@ func fileCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag
switch *contentType {
case "iso", "vztmpl":
uploadTimeout := d.Get(mkResourceVirtualEnvironmentFileTimeoutUpload).(int)
_, err = capi.Node(nodeName).Storage(datastoreID).APIUpload(ctx, request, uploadTimeout, config.TempDir())
if err != nil {
diags = append(diags, diag.FromErr(err)...)
return diags
}
default:
// For all other content types, we need to upload the file to the node's
// datastore using SFTP.
@ -565,14 +572,44 @@ func fileCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag
}...)
}
remoteFileDir := *datastore.Path
// the temp directory is used to store the file on the node before moving it to the datastore
// will be created if it does not exist
tempFileDir := fmt.Sprintf("/tmp/tfpve/%s", uuid.NewString())
err = capi.SSH().NodeUpload(ctx, nodeName, remoteFileDir, request)
}
err = capi.SSH().NodeUpload(ctx, nodeName, tempFileDir, request)
if err != nil {
diags = append(diags, diag.FromErr(err)...)
return diags
}
if err != nil {
diags = append(diags, diag.FromErr(err)...)
return diags
// handle the case where the file is uploaded to a subdirectory of the datastore
srcDir := tempFileDir
dstDir := *datastore.Path
if request.ContentType != "" {
srcDir = tempFileDir + "/" + request.ContentType
dstDir = *datastore.Path + "/" + request.ContentType
}
_, err := capi.SSH().ExecuteNodeCommands(ctx, nodeName, []string{
// the `mv` command should be scoped to the specific directories in sudoers!
fmt.Sprintf(`%s; try_sudo "mv %s/%s %s/%s" && rm %s/%s && rmdir -p %s`,
trySudo,
srcDir, *fileName,
dstDir, *fileName,
srcDir, *fileName,
srcDir,
),
})
if err != nil {
if matches, e := regexp.MatchString(`cannot move .* Permission denied`, err.Error()); e == nil && matches {
return diag.FromErr(newErrSSHUserNoPermission(capi.SSH().Username()))
}
diags = append(diags, diag.Errorf("error moving file: %s", err.Error())...)
return diags
}
}
volID, di := fileGetVolumeID(d)

View File

@ -0,0 +1,15 @@
package resource
import (
"fmt"
)
const (
trySudo = `try_sudo(){ if [ $(sudo -n pvesm apiinfo 2>&1 | grep "APIVER" | wc -l) -gt 0 ]; then sudo $1; else $1; fi }`
)
func newErrSSHUserNoPermission(username string) error {
return fmt.Errorf("the SSH user '%s' does not have required permissions. "+
"Make sure 'sudo' is installed and the user is configured in sudoers file. "+
"Refer to the documentation for more details", username)
}

View File

@ -11,6 +11,7 @@ import (
"encoding/base64"
"errors"
"fmt"
"regexp"
"sort"
"strconv"
"strings"
@ -2974,7 +2975,7 @@ func vmCreateCustomDisks(ctx context.Context, d *schema.ResourceData, m interfac
commands = append(
commands,
`set -e`,
`try_sudo(){ if [ $(sudo -n echo tfpve 2>&1 | grep "tfpve" | wc -l) -gt 0 ]; then sudo $1; else $1; fi }`,
trySudo,
fmt.Sprintf(`file_id="%s"`, fileID),
fmt.Sprintf(`file_format="%s"`, fileFormat),
fmt.Sprintf(`datastore_id_target="%s"`, datastoreID),
@ -3007,9 +3008,8 @@ func vmCreateCustomDisks(ctx context.Context, d *schema.ResourceData, m interfac
out, err := api.SSH().ExecuteNodeCommands(ctx, nodeName, commands)
if err != nil {
if strings.Contains(err.Error(), "pvesm: not found") {
return diag.Errorf("The configured SSH user '%s' does not have the required permissions to import disks. "+
"Make sure `sudo` is installated and the user is a member of sudoers.", api.SSH().Username())
if matches, e := regexp.Match(`pvesm: .* not found`, out); e == nil && matches {
return diag.FromErr(newErrSSHUserNoPermission(api.SSH().Username()))
}
return diag.FromErr(err)