Skip to content

Commit 4048233

Browse files
authored
ssh-based provisioner: Re-enable support for PowerShell (#37794)
* ssh-based provisioner: Re-enable support for PowerShell * add changelog entry
1 parent 9595517 commit 4048233

File tree

3 files changed

+153
-6
lines changed

3 files changed

+153
-6
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
kind: ENHANCEMENTS
2+
body: 'ssh-based provisioner (file + remote-exec): Re-enable support for PowerShell'
3+
time: 2025-10-22T16:29:09.342697+01:00
4+
custom:
5+
Issue: "37794"

internal/communicator/ssh/communicator.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ func (c *Communicator) Upload(path string, input io.Reader) error {
436436
return scpUploadFile(targetFile, input, w, stdoutR, size)
437437
}
438438

439-
cmd, err := quoteShell([]string{"scp", "-vt", targetDir}, c.connInfo.TargetPlatform)
439+
cmd, err := quoteScpCommand([]string{"scp", "-vt", targetDir}, c.connInfo.TargetPlatform)
440440
if err != nil {
441441
return err
442442
}
@@ -509,7 +509,7 @@ func (c *Communicator) UploadDir(dst string, src string) error {
509509
return uploadEntries()
510510
}
511511

512-
cmd, err := quoteShell([]string{"scp", "-rvt", dst}, c.connInfo.TargetPlatform)
512+
cmd, err := quoteScpCommand([]string{"scp", "-rvt", dst}, c.connInfo.TargetPlatform)
513513
if err != nil {
514514
return err
515515
}
@@ -886,14 +886,15 @@ func (c *bastionConn) Close() error {
886886
return c.Bastion.Close()
887887
}
888888

889-
func quoteShell(args []string, targetPlatform string) (string, error) {
889+
func quoteScpCommand(args []string, targetPlatform string) (string, error) {
890890
if targetPlatform == TargetPlatformUnix {
891891
return shquot.POSIXShell(args), nil
892892
}
893893
if targetPlatform == TargetPlatformWindows {
894-
return shquot.WindowsArgv(args), nil
894+
cmd, args := shquot.WindowsArgvSplit(args)
895+
return fmt.Sprintf("%s %s", cmd, args), nil
895896
}
896897

897-
return "", fmt.Errorf("Cannot quote shell command, target platform unknown: %s", targetPlatform)
898+
return "", fmt.Errorf("Cannot quote scp command, target platform unknown: %s", targetPlatform)
898899

899900
}

internal/communicator/ssh/communicator_test.go

Lines changed: 142 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"testing"
2424
"time"
2525

26+
"github.com/google/go-cmp/cmp"
2627
"github.com/hashicorp/terraform/internal/communicator/remote"
2728
"github.com/zclconf/go-cty/cty"
2829
"golang.org/x/crypto/ssh"
@@ -660,7 +661,7 @@ func TestAccHugeUploadFile(t *testing.T) {
660661
return scpUploadFile(targetFile, source, w, stdoutR, size)
661662
}
662663

663-
cmd, err := quoteShell([]string{"scp", "-vt", targetDir}, c.connInfo.TargetPlatform)
664+
cmd, err := quoteScpCommand([]string{"scp", "-vt", targetDir}, c.connInfo.TargetPlatform)
664665
if err != nil {
665666
t.Fatal(err)
666667
}
@@ -680,6 +681,146 @@ func TestAccHugeUploadFile(t *testing.T) {
680681
}
681682
}
682683

684+
func TestQuoteScpCommand(t *testing.T) {
685+
testCases := []struct {
686+
inputArgs []string
687+
platform string
688+
expectedCmd string
689+
}{
690+
// valid Unix command
691+
{
692+
[]string{"scp", "-vt", "/var/path"},
693+
TargetPlatformUnix,
694+
"'scp' -vt /var/path",
695+
},
696+
697+
// command injection attempt in Unix
698+
{
699+
[]string{"scp", "-vt", "/var/path;rm"},
700+
TargetPlatformUnix,
701+
"'scp' -vt /var/path\\;rm",
702+
},
703+
{
704+
[]string{"scp", "-vt", "/var/path&&rm"},
705+
TargetPlatformUnix,
706+
"'scp' -vt /var/path\\&\\&rm",
707+
},
708+
{
709+
[]string{"scp", "-vt", "/var/path|rm"},
710+
TargetPlatformUnix,
711+
"'scp' -vt /var/path\\|rm",
712+
},
713+
{
714+
[]string{"scp", "-vt", "/var/path||rm"},
715+
TargetPlatformUnix,
716+
"'scp' -vt /var/path\\|\\|rm",
717+
},
718+
{
719+
[]string{"scp", "-vt", "/var/path; rm"},
720+
TargetPlatformUnix,
721+
"'scp' -vt '/var/path; rm'",
722+
},
723+
{
724+
[]string{"scp", "-vt", "/var/path`rm`"},
725+
TargetPlatformUnix,
726+
"'scp' -vt /var/path\\`rm\\`",
727+
},
728+
{
729+
[]string{"scp", "-vt", "/var/path$(rm)"},
730+
TargetPlatformUnix,
731+
"'scp' -vt /var/path\\$\\(rm\\)",
732+
},
733+
734+
// valid Windows commands
735+
{
736+
[]string{"scp", "-vt", "C:\\Windows\\Temp"},
737+
TargetPlatformWindows,
738+
"scp -vt C:\\Windows\\Temp",
739+
},
740+
{
741+
[]string{"scp", "-vt", "C:\\Windows\\Temp With Space"},
742+
TargetPlatformWindows,
743+
"scp -vt \"C:\\Windows\\Temp With Space\"",
744+
},
745+
746+
// command injection attempt in Windows
747+
{
748+
[]string{"scp", "-vt", "C:\\Windows\\Temp ;rmdir"},
749+
TargetPlatformWindows,
750+
"scp -vt \"C:\\Windows\\Temp ;rmdir\"",
751+
},
752+
{
753+
[]string{"scp", "-vt", "C:\\Windows\\Temp\";rmdir"},
754+
TargetPlatformWindows,
755+
"scp -vt \"C:\\Windows\\Temp\\\";rmdir\"",
756+
},
757+
{
758+
[]string{"scp", "-vt", "C:\\Windows\\Temp\nrmdir"},
759+
TargetPlatformWindows,
760+
"scp -vt \"C:\\Windows\\Temp\nrmdir\"",
761+
},
762+
{
763+
[]string{"scp", "-vt", "C:\\Windows\\Temp\trmdir"},
764+
TargetPlatformWindows,
765+
"scp -vt \"C:\\Windows\\Temp\trmdir\"",
766+
},
767+
{
768+
[]string{"scp", "-vt", "C:\\Windows\\Temp\vrmdir"},
769+
TargetPlatformWindows,
770+
"scp -vt \"C:\\Windows\\Temp\vrmdir\"",
771+
},
772+
{
773+
[]string{"scp", "-vt", "C:\\Windows\\Temp\u0020rmdir"},
774+
TargetPlatformWindows,
775+
"scp -vt \"C:\\Windows\\Temp rmdir\"",
776+
},
777+
778+
// There is no special handling of the injection attempts below
779+
// but we include them anyway to demonstrate this
780+
// and to avoid any regressions due to upstream changes.
781+
{
782+
[]string{"scp", "-vt", "C:\\Windows\\Temp;rmdir"},
783+
TargetPlatformWindows,
784+
"scp -vt C:\\Windows\\Temp;rmdir",
785+
},
786+
{
787+
[]string{"scp", "-vt", "C:\\Windows\\Temp&rmdir"},
788+
TargetPlatformWindows,
789+
"scp -vt C:\\Windows\\Temp&rmdir",
790+
},
791+
{
792+
[]string{"scp", "-vt", "C:\\Windows\\Temp&&rmdir"},
793+
TargetPlatformWindows,
794+
"scp -vt C:\\Windows\\Temp&&rmdir",
795+
},
796+
{
797+
[]string{"scp", "-vt", "C:\\Windows\\Temp|rmdir"},
798+
TargetPlatformWindows,
799+
"scp -vt C:\\Windows\\Temp|rmdir",
800+
},
801+
{
802+
[]string{"scp", "-vt", "C:\\Windows\\Temp||rmdir"},
803+
TargetPlatformWindows,
804+
"scp -vt C:\\Windows\\Temp||rmdir",
805+
},
806+
{
807+
[]string{"scp", "-vt", "C:\\Windows\\Temp$(rmdir)"},
808+
TargetPlatformWindows,
809+
"scp -vt C:\\Windows\\Temp$(rmdir)",
810+
},
811+
}
812+
813+
for _, tc := range testCases {
814+
cmd, err := quoteScpCommand(tc.inputArgs, tc.platform)
815+
if err != nil {
816+
t.Fatal(err)
817+
}
818+
if diff := cmp.Diff(tc.expectedCmd, cmd); diff != "" {
819+
t.Fatalf("unexpected command for %q: %s", tc.inputArgs, diff)
820+
}
821+
}
822+
}
823+
683824
func TestScriptPath(t *testing.T) {
684825
cases := []struct {
685826
Input string

0 commit comments

Comments
 (0)