github.com/opentofu/opentofu@v1.7.1/internal/communicator/winrm/communicator.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package winrm 7 8 import ( 9 "fmt" 10 "io" 11 "log" 12 "math/rand" 13 "strconv" 14 "strings" 15 "time" 16 17 "github.com/masterzen/winrm" 18 "github.com/opentofu/opentofu/internal/communicator/remote" 19 "github.com/opentofu/opentofu/internal/provisioners" 20 "github.com/packer-community/winrmcp/winrmcp" 21 "github.com/zclconf/go-cty/cty" 22 ) 23 24 // Communicator represents the WinRM communicator 25 type Communicator struct { 26 connInfo *connectionInfo 27 client *winrm.Client 28 endpoint *winrm.Endpoint 29 rand *rand.Rand 30 } 31 32 // New creates a new communicator implementation over WinRM. 33 func New(v cty.Value) (*Communicator, error) { 34 connInfo, err := parseConnectionInfo(v) 35 if err != nil { 36 return nil, err 37 } 38 39 endpoint := &winrm.Endpoint{ 40 Host: connInfo.Host, 41 Port: int(connInfo.Port), 42 HTTPS: connInfo.HTTPS, 43 Insecure: connInfo.Insecure, 44 Timeout: connInfo.TimeoutVal, 45 } 46 if len(connInfo.CACert) > 0 { 47 endpoint.CACert = []byte(connInfo.CACert) 48 } 49 50 comm := &Communicator{ 51 connInfo: connInfo, 52 endpoint: endpoint, 53 // Seed our own rand source so that script paths are not deterministic 54 rand: rand.New(rand.NewSource(time.Now().UnixNano())), 55 } 56 57 return comm, nil 58 } 59 60 // Connect implementation of communicator.Communicator interface 61 func (c *Communicator) Connect(o provisioners.UIOutput) error { 62 // Set the client to nil since we'll (re)create it 63 c.client = nil 64 65 params := winrm.DefaultParameters 66 params.Timeout = formatDuration(c.Timeout()) 67 if c.connInfo.NTLM { 68 params.TransportDecorator = func() winrm.Transporter { return &winrm.ClientNTLM{} } 69 } 70 71 client, err := winrm.NewClientWithParameters( 72 c.endpoint, c.connInfo.User, c.connInfo.Password, params) 73 if err != nil { 74 return err 75 } 76 77 if o != nil { 78 o.Output(fmt.Sprintf( 79 "Connecting to remote host via WinRM...\n"+ 80 " Host: %s\n"+ 81 " Port: %d\n"+ 82 " User: %s\n"+ 83 " Password: %t\n"+ 84 " HTTPS: %t\n"+ 85 " Insecure: %t\n"+ 86 " NTLM: %t\n"+ 87 " CACert: %t", 88 c.connInfo.Host, 89 c.connInfo.Port, 90 c.connInfo.User, 91 c.connInfo.Password != "", 92 c.connInfo.HTTPS, 93 c.connInfo.Insecure, 94 c.connInfo.NTLM, 95 c.connInfo.CACert != "", 96 )) 97 } 98 99 log.Printf("[DEBUG] connecting to remote shell using WinRM") 100 shell, err := client.CreateShell() 101 if err != nil { 102 log.Printf("[ERROR] error creating shell: %s", err) 103 return err 104 } 105 106 err = shell.Close() 107 if err != nil { 108 log.Printf("[ERROR] error closing shell: %s", err) 109 return err 110 } 111 112 if o != nil { 113 o.Output("Connected!") 114 } 115 116 c.client = client 117 118 return nil 119 } 120 121 // Disconnect implementation of communicator.Communicator interface 122 func (c *Communicator) Disconnect() error { 123 c.client = nil 124 return nil 125 } 126 127 // Timeout implementation of communicator.Communicator interface 128 func (c *Communicator) Timeout() time.Duration { 129 return c.connInfo.TimeoutVal 130 } 131 132 // ScriptPath implementation of communicator.Communicator interface 133 func (c *Communicator) ScriptPath() string { 134 return strings.Replace( 135 c.connInfo.ScriptPath, "%RAND%", 136 strconv.FormatInt(int64(c.rand.Int31()), 10), -1) 137 } 138 139 // Start implementation of communicator.Communicator interface 140 func (c *Communicator) Start(rc *remote.Cmd) error { 141 rc.Init() 142 log.Printf("[DEBUG] starting remote command: %s", rc.Command) 143 144 // TODO: make sure communicators always connect first, so we can get output 145 // from the connection. 146 if c.client == nil { 147 log.Println("[WARN] winrm client not connected, attempting to connect") 148 if err := c.Connect(nil); err != nil { 149 return err 150 } 151 } 152 153 status, err := c.client.Run(rc.Command, rc.Stdout, rc.Stderr) 154 rc.SetExitStatus(status, err) 155 156 return nil 157 } 158 159 // Upload implementation of communicator.Communicator interface 160 func (c *Communicator) Upload(path string, input io.Reader) error { 161 wcp, err := c.newCopyClient() 162 if err != nil { 163 return err 164 } 165 log.Printf("[DEBUG] Uploading file to '%s'", path) 166 return wcp.Write(path, input) 167 } 168 169 // UploadScript implementation of communicator.Communicator interface 170 func (c *Communicator) UploadScript(path string, input io.Reader) error { 171 return c.Upload(path, input) 172 } 173 174 // UploadDir implementation of communicator.Communicator interface 175 func (c *Communicator) UploadDir(dst string, src string) error { 176 log.Printf("[DEBUG] Uploading dir '%s' to '%s'", src, dst) 177 wcp, err := c.newCopyClient() 178 if err != nil { 179 return err 180 } 181 return wcp.Copy(src, dst) 182 } 183 184 func (c *Communicator) newCopyClient() (*winrmcp.Winrmcp, error) { 185 addr := fmt.Sprintf("%s:%d", c.endpoint.Host, c.endpoint.Port) 186 187 config := winrmcp.Config{ 188 Auth: winrmcp.Auth{ 189 User: c.connInfo.User, 190 Password: c.connInfo.Password, 191 }, 192 Https: c.connInfo.HTTPS, 193 Insecure: c.connInfo.Insecure, 194 OperationTimeout: c.Timeout(), 195 MaxOperationsPerShell: 15, // lowest common denominator 196 } 197 198 if c.connInfo.NTLM { 199 config.TransportDecorator = func() winrm.Transporter { return &winrm.ClientNTLM{} } 200 } 201 202 if c.connInfo.CACert != "" { 203 config.CACertBytes = []byte(c.connInfo.CACert) 204 } 205 206 return winrmcp.New(addr, &config) 207 }