ssh-based provisioner: Re-enable support for PowerShell (#37794)
Some checks failed
build / Determine intended Terraform version (push) Has been cancelled
build / Determine Go toolchain version (push) Has been cancelled
Quick Checks / Unit Tests (push) Has been cancelled
Quick Checks / Race Tests (push) Has been cancelled
Quick Checks / End-to-end Tests (push) Has been cancelled
Quick Checks / Code Consistency Checks (push) Has been cancelled
build / Generate release metadata (push) Has been cancelled
build / Build for freebsd_386 (push) Has been cancelled
build / Build for linux_386 (push) Has been cancelled
build / Build for openbsd_386 (push) Has been cancelled
build / Build for windows_386 (push) Has been cancelled
build / Build for darwin_amd64 (push) Has been cancelled
build / Build for freebsd_amd64 (push) Has been cancelled
build / Build for linux_amd64 (push) Has been cancelled
build / Build for openbsd_amd64 (push) Has been cancelled
build / Build for solaris_amd64 (push) Has been cancelled
build / Build for windows_amd64 (push) Has been cancelled
build / Build for freebsd_arm (push) Has been cancelled
build / Build for linux_arm (push) Has been cancelled
build / Build for darwin_arm64 (push) Has been cancelled
build / Build for linux_arm64 (push) Has been cancelled
build / Build for windows_arm64 (push) Has been cancelled
build / Build Docker image for linux_386 (push) Has been cancelled
build / Build Docker image for linux_amd64 (push) Has been cancelled
build / Build Docker image for linux_arm (push) Has been cancelled
build / Build Docker image for linux_arm64 (push) Has been cancelled
build / Build e2etest for linux_386 (push) Has been cancelled
build / Build e2etest for windows_386 (push) Has been cancelled
build / Build e2etest for darwin_amd64 (push) Has been cancelled
build / Build e2etest for linux_amd64 (push) Has been cancelled
build / Build e2etest for windows_amd64 (push) Has been cancelled
build / Build e2etest for linux_arm (push) Has been cancelled
build / Build e2etest for darwin_arm64 (push) Has been cancelled
build / Build e2etest for linux_arm64 (push) Has been cancelled
build / Run e2e test for linux_386 (push) Has been cancelled
build / Run e2e test for windows_386 (push) Has been cancelled
build / Run e2e test for darwin_amd64 (push) Has been cancelled
build / Run e2e test for linux_amd64 (push) Has been cancelled
build / Run e2e test for windows_amd64 (push) Has been cancelled
build / Run e2e test for linux_arm (push) Has been cancelled
build / Run e2e test for linux_arm64 (push) Has been cancelled
build / Run terraform-exec test for linux amd64 (push) Has been cancelled

* ssh-based provisioner: Re-enable support for PowerShell

* add changelog entry
This commit is contained in:
Radek Simko 2025-11-06 17:45:13 +00:00 committed by GitHub
parent 9595517730
commit 40482337a7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 153 additions and 6 deletions

View file

@ -0,0 +1,5 @@
kind: ENHANCEMENTS
body: 'ssh-based provisioner (file + remote-exec): Re-enable support for PowerShell'
time: 2025-10-22T16:29:09.342697+01:00
custom:
Issue: "37794"

View file

@ -436,7 +436,7 @@ func (c *Communicator) Upload(path string, input io.Reader) error {
return scpUploadFile(targetFile, input, w, stdoutR, size)
}
cmd, err := quoteShell([]string{"scp", "-vt", targetDir}, c.connInfo.TargetPlatform)
cmd, err := quoteScpCommand([]string{"scp", "-vt", targetDir}, c.connInfo.TargetPlatform)
if err != nil {
return err
}
@ -509,7 +509,7 @@ func (c *Communicator) UploadDir(dst string, src string) error {
return uploadEntries()
}
cmd, err := quoteShell([]string{"scp", "-rvt", dst}, c.connInfo.TargetPlatform)
cmd, err := quoteScpCommand([]string{"scp", "-rvt", dst}, c.connInfo.TargetPlatform)
if err != nil {
return err
}
@ -886,14 +886,15 @@ func (c *bastionConn) Close() error {
return c.Bastion.Close()
}
func quoteShell(args []string, targetPlatform string) (string, error) {
func quoteScpCommand(args []string, targetPlatform string) (string, error) {
if targetPlatform == TargetPlatformUnix {
return shquot.POSIXShell(args), nil
}
if targetPlatform == TargetPlatformWindows {
return shquot.WindowsArgv(args), nil
cmd, args := shquot.WindowsArgvSplit(args)
return fmt.Sprintf("%s %s", cmd, args), nil
}
return "", fmt.Errorf("Cannot quote shell command, target platform unknown: %s", targetPlatform)
return "", fmt.Errorf("Cannot quote scp command, target platform unknown: %s", targetPlatform)
}

View file

@ -23,6 +23,7 @@ import (
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform/internal/communicator/remote"
"github.com/zclconf/go-cty/cty"
"golang.org/x/crypto/ssh"
@ -660,7 +661,7 @@ func TestAccHugeUploadFile(t *testing.T) {
return scpUploadFile(targetFile, source, w, stdoutR, size)
}
cmd, err := quoteShell([]string{"scp", "-vt", targetDir}, c.connInfo.TargetPlatform)
cmd, err := quoteScpCommand([]string{"scp", "-vt", targetDir}, c.connInfo.TargetPlatform)
if err != nil {
t.Fatal(err)
}
@ -680,6 +681,146 @@ func TestAccHugeUploadFile(t *testing.T) {
}
}
func TestQuoteScpCommand(t *testing.T) {
testCases := []struct {
inputArgs []string
platform string
expectedCmd string
}{
// valid Unix command
{
[]string{"scp", "-vt", "/var/path"},
TargetPlatformUnix,
"'scp' -vt /var/path",
},
// command injection attempt in Unix
{
[]string{"scp", "-vt", "/var/path;rm"},
TargetPlatformUnix,
"'scp' -vt /var/path\\;rm",
},
{
[]string{"scp", "-vt", "/var/path&&rm"},
TargetPlatformUnix,
"'scp' -vt /var/path\\&\\&rm",
},
{
[]string{"scp", "-vt", "/var/path|rm"},
TargetPlatformUnix,
"'scp' -vt /var/path\\|rm",
},
{
[]string{"scp", "-vt", "/var/path||rm"},
TargetPlatformUnix,
"'scp' -vt /var/path\\|\\|rm",
},
{
[]string{"scp", "-vt", "/var/path; rm"},
TargetPlatformUnix,
"'scp' -vt '/var/path; rm'",
},
{
[]string{"scp", "-vt", "/var/path`rm`"},
TargetPlatformUnix,
"'scp' -vt /var/path\\`rm\\`",
},
{
[]string{"scp", "-vt", "/var/path$(rm)"},
TargetPlatformUnix,
"'scp' -vt /var/path\\$\\(rm\\)",
},
// valid Windows commands
{
[]string{"scp", "-vt", "C:\\Windows\\Temp"},
TargetPlatformWindows,
"scp -vt C:\\Windows\\Temp",
},
{
[]string{"scp", "-vt", "C:\\Windows\\Temp With Space"},
TargetPlatformWindows,
"scp -vt \"C:\\Windows\\Temp With Space\"",
},
// command injection attempt in Windows
{
[]string{"scp", "-vt", "C:\\Windows\\Temp ;rmdir"},
TargetPlatformWindows,
"scp -vt \"C:\\Windows\\Temp ;rmdir\"",
},
{
[]string{"scp", "-vt", "C:\\Windows\\Temp\";rmdir"},
TargetPlatformWindows,
"scp -vt \"C:\\Windows\\Temp\\\";rmdir\"",
},
{
[]string{"scp", "-vt", "C:\\Windows\\Temp\nrmdir"},
TargetPlatformWindows,
"scp -vt \"C:\\Windows\\Temp\nrmdir\"",
},
{
[]string{"scp", "-vt", "C:\\Windows\\Temp\trmdir"},
TargetPlatformWindows,
"scp -vt \"C:\\Windows\\Temp\trmdir\"",
},
{
[]string{"scp", "-vt", "C:\\Windows\\Temp\vrmdir"},
TargetPlatformWindows,
"scp -vt \"C:\\Windows\\Temp\vrmdir\"",
},
{
[]string{"scp", "-vt", "C:\\Windows\\Temp\u0020rmdir"},
TargetPlatformWindows,
"scp -vt \"C:\\Windows\\Temp rmdir\"",
},
// There is no special handling of the injection attempts below
// but we include them anyway to demonstrate this
// and to avoid any regressions due to upstream changes.
{
[]string{"scp", "-vt", "C:\\Windows\\Temp;rmdir"},
TargetPlatformWindows,
"scp -vt C:\\Windows\\Temp;rmdir",
},
{
[]string{"scp", "-vt", "C:\\Windows\\Temp&rmdir"},
TargetPlatformWindows,
"scp -vt C:\\Windows\\Temp&rmdir",
},
{
[]string{"scp", "-vt", "C:\\Windows\\Temp&&rmdir"},
TargetPlatformWindows,
"scp -vt C:\\Windows\\Temp&&rmdir",
},
{
[]string{"scp", "-vt", "C:\\Windows\\Temp|rmdir"},
TargetPlatformWindows,
"scp -vt C:\\Windows\\Temp|rmdir",
},
{
[]string{"scp", "-vt", "C:\\Windows\\Temp||rmdir"},
TargetPlatformWindows,
"scp -vt C:\\Windows\\Temp||rmdir",
},
{
[]string{"scp", "-vt", "C:\\Windows\\Temp$(rmdir)"},
TargetPlatformWindows,
"scp -vt C:\\Windows\\Temp$(rmdir)",
},
}
for _, tc := range testCases {
cmd, err := quoteScpCommand(tc.inputArgs, tc.platform)
if err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(tc.expectedCmd, cmd); diff != "" {
t.Fatalf("unexpected command for %q: %s", tc.inputArgs, diff)
}
}
}
func TestScriptPath(t *testing.T) {
cases := []struct {
Input string