README ¶
WinRM for Go
Note: if you're looking for the winrm
command-line tool, this has been splitted from this project and is available at winrm-cli
This is a Go library to execute remote commands on Windows machines through the use of WinRM/WinRS.
Note: this library doesn't support domain users (it doesn't support GSSAPI nor Kerberos). It's primary target is to execute remote commands on EC2 windows machines.
Contact
Getting Started
WinRM is available on Windows Server 2008 and up. This project natively supports basic authentication for local accounts, see the steps in the next section on how to prepare the remote Windows machine for this scenario. The authentication model is pluggable, see below for an example on using Negotiate/NTLM authentication (e.g. for connecting to vanilla Azure VMs) or Kerberos authentication (using domain accounts).
Note: This library only supports Golang 1.7+
Preparing the remote Windows machine for Basic authentication
This project supports only basic authentication for local accounts (domain users are not supported). The remote windows system must be prepared for winrm:
For a PowerShell script to do what is described below in one go, check Richard Downer's blog
On the remote host, a PowerShell prompt, using the Run as Administrator option and paste in the following lines:
winrm quickconfig
y
winrm set winrm/config/service/Auth '@{Basic="true"}'
winrm set winrm/config/service '@{AllowUnencrypted="true"}'
winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="1024"}'
N.B.: The Windows Firewall needs to be running to run this command. See Microsoft Knowledge Base article #2004640.
N.B.: Do not disable Negotiate authentication as the winrm
command itself uses this for internal authentication, and you risk getting a system where winrm
doesn't work anymore.
N.B.: The MaxMemoryPerShellMB
option has no effects on some Windows 2008R2 systems because of a WinRM bug. Make sure to install the hotfix described Microsoft Knowledge Base article #2842230 if you need to run commands that use more than 150MB of memory.
For more information on WinRM, please refer to the online documentation at Microsoft's DevCenter.
Preparing the remote Windows machine for kerberos authentication
This project supports domain users via kerberos authentication. The remote windows system must be prepared for winrm:
On the remote host, a PowerShell prompt, using the Run as Administrator option and paste in the following lines:
winrm quickconfig
y
winrm set winrm/config/service '@{AllowUnencrypted="true"}'
winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="1024"}'
All N.B points of "Preparing the remote Windows machine for Basic authentication" also applies.
Building the winrm go and executable
You can build winrm from source:
git clone https://github.com/masterzen/winrm
cd winrm
make
Note: this winrm code doesn't depend anymore on Gokogiri which means it is now in pure Go.
Note: you need go 1.5+. Please check your installation with
go version
Command-line usage
For command-line usage check the winrm-cli project
Library Usage
Warning the API might be subject to change.
For the fast version (this doesn't allow to send input to the command) and it's using HTTP as the transport:
package main
import (
"github.com/masterzen/winrm"
"os"
)
endpoint := winrm.NewEndpoint(host, 5986, false, false, nil, nil, nil, 0)
client, err := winrm.NewClient(endpoint, "Administrator", "secret")
if err != nil {
panic(err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
client.RunWithContext(ctx, "ipconfig /all", os.Stdout, os.Stderr)
or
package main
import (
"github.com/masterzen/winrm"
"fmt"
"os"
)
endpoint := winrm.NewEndpoint("localhost", 5985, false, false, nil, nil, nil, 0)
client, err := winrm.NewClient(endpoint,"Administrator", "secret")
if err != nil {
panic(err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
_, err := client.RunWithContextWithInput(ctx, "ipconfig", os.Stdout, os.Stderr, os.Stdin)
if err != nil {
panic(err)
}
By passing a TransportDecorator in the Parameters struct it is possible to use different Transports (e.g. NTLM)
package main
import (
"github.com/masterzen/winrm"
"fmt"
"os"
)
endpoint := winrm.NewEndpoint("localhost", 5985, false, false, nil, nil, nil, 0)
params := DefaultParameters
params.TransportDecorator = func() Transporter { return &ClientNTLM{} }
client, err := NewClientWithParameters(endpoint, "test", "test", params)
if err != nil {
panic(err)
}
_, err := client.RunWithInput("ipconfig", os.Stdout, os.Stderr, os.Stdin)
if err != nil {
panic(err)
}
Passing a TransportDecorator also permit to use Kerberos authentication
package main
import (
"os"
"fmt"
"github.com/masterzen/winrm"
)
endpoint := winrm.NewEndpoint("srv-win", 5985, false, false, nil, nil, nil, 0)
params := winrm.DefaultParameters
params.TransportDecorator = func() Transporter {
return &winrm.ClientKerberos{
Username: "test",
Password: "s3cr3t",
Hostname: "srv-win",
Realm: "DOMAIN.LAN",
Port: 5985,
Proto: "http",
KrbConf: "/etc/krb5.conf",
SPN: fmt.Sprintf("HTTP/%s", hostname),
}
}
client, err := NewClientWithParameters(endpoint, "test", "s3cr3t", params)
if err != nil {
panic(err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
_, err := client.RunWithContextWithInput(ctx, "ipconfig", os.Stdout, os.Stderr, os.Stdin)
if err != nil {
panic(err)
}
By passing a Dial in the Parameters struct it is possible to use different dialer (e.g. tunnel through SSH)
package main
import (
"github.com/masterzen/winrm"
"golang.org/x/crypto/ssh"
"os"
)
func main() {
sshClient, err := ssh.Dial("tcp","localhost:22", &ssh.ClientConfig{
User:"ubuntu",
Auth: []ssh.AuthMethod{ssh.Password("ubuntu")},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
})
endpoint := winrm.NewEndpoint("other-host", 5985, false, false, nil, nil, nil, 0)
params := winrm.DefaultParameters
params.Dial = sshClient.Dial
client, err := winrm.NewClientWithParameters(endpoint, "test", "test", params)
if err != nil {
panic(err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
_, err = client.RunWithContextWithInput(ctx, "ipconfig", os.Stdout, os.Stderr, os.Stdin)
if err != nil {
panic(err)
}
}
For a more complex example, it is possible to call the various functions directly:
package main
import (
"github.com/masterzen/winrm"
"fmt"
"bytes"
"os"
)
stdin := bytes.NewBufferString("ipconfig /all")
endpoint := winrm.NewEndpoint("localhost", 5985, false, false,nil, nil, nil, 0)
client , err := winrm.NewClient(endpoint, "Administrator", "secret")
if err != nil {
panic(err)
}
shell, err := client.CreateShell()
if err != nil {
panic(err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var cmd *winrm.Command
cmd, err = shell.ExecuteWithContext(ctx, "cmd.exe")
if err != nil {
panic(err)
}
go io.Copy(cmd.Stdin, stdin)
go io.Copy(os.Stdout, cmd.Stdout)
go io.Copy(os.Stderr, cmd.Stderr)
cmd.Wait()
shell.Close()
For using HTTPS authentication with x 509 cert without checking the CA
package main
import (
"github.com/masterzen/winrm"
"io/ioutil"
"log"
"os"
)
func main() {
clientCert, err := ioutil.ReadFile("/home/example/winrm_client_cert.pem")
if err != nil {
log.Fatalf("failed to read client certificate: %q", err)
}
clientKey, err := ioutil.ReadFile("/home/example/winrm_client_key.pem")
if err != nil {
log.Fatalf("failed to read client key: %q", err)
}
winrm.DefaultParameters.TransportDecorator = func() winrm.Transporter {
// winrm https module
return &winrm.ClientAuthRequest{}
}
endpoint := winrm.NewEndpoint(
"192.168.100.2", // host to connect to
5986, // winrm port
true, // use TLS
true, // Allow insecure connection
nil, // CA certificate
clientCert, // Client Certificate
clientKey, // Client Key
0, // Timeout
)
client, err := winrm.NewClient(endpoint, "Administrator", "")
if err != nil {
log.Fatalf("failed to create client: %q", err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
_, err = client.RunWithContext(ctx, "whoami", os.Stdout, os.Stderr)
if err != nil {
log.Fatalf("failed to run command: %q", err)
}
}
Note: canceling the context.Context
passed as first argument to the various
functions of the API will not cancel the HTTP requests themselves, it will
rather cause a running command to be aborted on the remote machine via a call to
command.Stop()
.
Developing on WinRM
If you wish to work on winrm
itself, you'll first need Go
installed (version 1.5+ is required). Make sure you have Go properly installed,
including setting up your GOPATH.
For some additional dependencies, Go needs Mercurial and Bazaar to be installed. Winrm itself doesn't require these, but a dependency of a dependency does.
Next, clone this repository into $GOPATH/src/github.com/masterzen/winrm
and
then just type make
.
You can run tests by typing make test
.
If you make any changes to the code, run make format
in order to automatically
format the code according to Go standards.
When new dependencies are added to winrm you can use make updatedeps
to
get the latest and subsequently use make
to compile.
Documentation ¶
Index ¶
- Variables
- func NewClientWithDial(dial func(network, addr string) (net.Conn, error)) *clientRequest
- func NewClientWithProxyFunc(proxyfunc func(req *http.Request) (*url.URL, error)) *clientRequest
- func NewDeleteShellRequest(uri, shellID string, params *Parameters) *soap.SoapMessage
- func NewExecuteCommandRequest(uri, shellID, command string, arguments []string, params *Parameters) *soap.SoapMessage
- func NewGetOutputRequest(uri, shellID, commandID, streams string, params *Parameters) *soap.SoapMessage
- func NewOpenShellRequest(uri string, params *Parameters) *soap.SoapMessage
- func NewSendInputRequest(uri, shellID, commandID string, input []byte, eof bool, params *Parameters) *soap.SoapMessage
- func NewSignalRequest(uri string, shellID string, commandID string, params *Parameters) *soap.SoapMessage
- func ParseExecuteCommandResponse(response string) (commandId string, err error)
- func ParseOpenShellResponse(response string) (string, error)
- func ParseSlurpOutputErrResponse(response string, stdout, stderr io.Writer) (bool, int, error)
- func ParseSlurpOutputResponse(response string, stream io.Writer, streamType string) (bool, int, error)
- func Powershell(psCmd string) string
- type Client
- func (c *Client) CreateShell() (*Shell, error)
- func (c *Client) NewShell(id string) *Shell
- func (c *Client) Run(command string, stdout io.Writer, stderr io.Writer) (int, error)deprecated
- func (c *Client) RunCmdWithContext(ctx context.Context, command string) (string, string, int, error)
- func (c *Client) RunPSWithContext(ctx context.Context, command string) (string, string, int, error)
- func (c *Client) RunPSWithContextWithString(ctx context.Context, command string, stdin string) (string, string, int, error)
- func (c *Client) RunPSWithString(command string, stdin string) (string, string, int, error)deprecated
- func (c *Client) RunWithContext(ctx context.Context, command string, stdout io.Writer, stderr io.Writer) (int, error)
- func (c *Client) RunWithContextWithInput(ctx context.Context, command string, stdout, stderr io.Writer, stdin io.Reader) (int, error)
- func (c *Client) RunWithContextWithString(ctx context.Context, command string, stdin string) (string, string, int, error)
- func (c *Client) RunWithInput(command string, stdout, stderr io.Writer, stdin io.Reader) (int, error)deprecated
- func (c *Client) RunWithString(command string, stdin string) (string, string, int, error)deprecated
- type ClientAuthRequest
- type ClientKerberos
- type ClientNTLM
- type Command
- type Encryption
- func (e *Encryption) ParseEncryptedResponse(response *http.Response) ([]byte, error)
- func (e *Encryption) Post(client *Client, message *soap.SoapMessage) (string, error)
- func (e *Encryption) PrepareEncryptedRequest(client *Client, endpoint string, message []byte) (string, error)
- func (e *Encryption) PrepareRequest(client *Client, endpoint string) error
- func (e *Encryption) Transport(endpoint *Endpoint) error
- type Endpoint
- type ExecuteCommandError
- type Parameters
- type Settings
- type Shell
- type Transporter
Constants ¶
This section is empty.
Variables ¶
var DefaultParameters = NewParameters("PT60S", "en-US", 153600)
DefaultParameters return constant config of type Parameters
Functions ¶
func NewClientWithDial ¶
NewClientWithDial NewClientWithDial
func NewClientWithProxyFunc ¶
NewClientWithProxyFunc NewClientWithProxyFunc
func NewDeleteShellRequest ¶
func NewDeleteShellRequest(uri, shellID string, params *Parameters) *soap.SoapMessage
NewDeleteShellRequest ...
func NewExecuteCommandRequest ¶
func NewExecuteCommandRequest(uri, shellID, command string, arguments []string, params *Parameters) *soap.SoapMessage
NewExecuteCommandRequest exec command on specific shellID
func NewGetOutputRequest ¶
func NewGetOutputRequest(uri, shellID, commandID, streams string, params *Parameters) *soap.SoapMessage
NewGetOutputRequest NewGetOutputRequest
func NewOpenShellRequest ¶
func NewOpenShellRequest(uri string, params *Parameters) *soap.SoapMessage
NewOpenShellRequest makes a new soap request
func NewSendInputRequest ¶
func NewSendInputRequest(uri, shellID, commandID string, input []byte, eof bool, params *Parameters) *soap.SoapMessage
NewSendInputRequest NewSendInputRequest
func NewSignalRequest ¶
func NewSignalRequest(uri string, shellID string, commandID string, params *Parameters) *soap.SoapMessage
NewSignalRequest NewSignalRequest
func ParseExecuteCommandResponse ¶
ParseExecuteCommandResponse ParseExecuteCommandResponse
func ParseOpenShellResponse ¶
ParseOpenShellResponse ParseOpenShellResponse
func ParseSlurpOutputErrResponse ¶
ParseSlurpOutputErrResponse ParseSlurpOutputErrResponse
func ParseSlurpOutputResponse ¶
func ParseSlurpOutputResponse(response string, stream io.Writer, streamType string) (bool, int, error)
ParseSlurpOutputResponse ParseSlurpOutputResponse
func Powershell ¶
Powershell wraps a PowerShell script and prepares it for execution by the winrm client
Types ¶
type Client ¶
type Client struct { Parameters // contains filtered or unexported fields }
Client struct
func NewClient ¶
NewClient will create a new remote client on url, connecting with user and password This function doesn't connect (connection happens only when CreateShell is called)
func NewClientWithParameters ¶
func NewClientWithParameters(endpoint *Endpoint, user, password string, params *Parameters) (*Client, error)
NewClientWithParameters will create a new remote client on url, connecting with user and password This function doesn't connect (connection happens only when CreateShell is called)
func (*Client) CreateShell ¶
CreateShell will create a WinRM Shell, which is the prealable for running commands.
func (*Client) Run
deprecated
func (*Client) RunCmdWithContext ¶
func (c *Client) RunCmdWithContext(ctx context.Context, command string) (string, string, int, error)
RunCmdWithContext will run command on the the remote host, returning the process stdout and stderr as strings If the context is canceled, the remote command is canceled.
func (*Client) RunPSWithContext ¶
RunPSWithContext will basically wrap your code to execute commands in powershell.exe. runs commands in cmd.exe
func (*Client) RunPSWithContextWithString ¶
func (c *Client) RunPSWithContextWithString(ctx context.Context, command string, stdin string) (string, string, int, error)
RunPSWithContextWithString will basically wrap your code to execute commands in powershell.exe. Default RunWithString runs commands in cmd.exe
func (*Client) RunPSWithString
deprecated
func (*Client) RunWithContext ¶
func (c *Client) RunWithContext(ctx context.Context, command string, stdout io.Writer, stderr io.Writer) (int, error)
RunWithContext will run command on the the remote host, writing the process stdout and stderr to the given writers. Note with this method it isn't possible to inject stdin. If the context is canceled, the remote command is canceled.
func (*Client) RunWithContextWithInput ¶
func (c *Client) RunWithContextWithInput(ctx context.Context, command string, stdout, stderr io.Writer, stdin io.Reader) (int, error)
RunWithContextWithInput will run command on the the remote host, writing the process stdout and stderr to the given writers, and injecting the process stdin with the stdin reader. If the context is canceled, the command on the remote machine is canceled. Warning stdin (not stdout/stderr) are bufferized, which means reading only one byte in stdin will send a winrm http packet to the remote host. If stdin is a pipe, it might be better for performance reasons to buffer it. If stdin is nil, this is equivalent to c.RunWithContext()
func (*Client) RunWithContextWithString ¶
func (c *Client) RunWithContextWithString(ctx context.Context, command string, stdin string) (string, string, int, error)
RunWithContextWithString will run command on the the remote host, returning the process stdout and stderr as strings, and using the input stdin string as the process input If the context is canceled, the remote command is canceled.
func (*Client) RunWithInput
deprecated
func (c *Client) RunWithInput(command string, stdout, stderr io.Writer, stdin io.Reader) (int, error)
RunWithInput will run command on the the remote host, writing the process stdout and stderr to the given writers, and injecting the process stdin with the stdin reader. Warning stdin (not stdout/stderr) are bufferized, which means reading only one byte in stdin will send a winrm http packet to the remote host. If stdin is a pipe, it might be better for performance reasons to buffer it. If stdin is nil, this is equivalent to c.Run()
Deprecated: use RunWithContextWithInput()
func (*Client) RunWithString
deprecated
type ClientAuthRequest ¶
type ClientAuthRequest struct {
// contains filtered or unexported fields
}
ClientAuthRequest ClientAuthRequest
func NewClientAuthRequestWithDial ¶
func NewClientAuthRequestWithDial(dial func(network, addr string) (net.Conn, error)) *ClientAuthRequest
NewClientAuthRequestWithDial NewClientAuthRequestWithDial
func (ClientAuthRequest) Post ¶
func (c ClientAuthRequest) Post(client *Client, request *soap.SoapMessage) (string, error)
Post Post
func (*ClientAuthRequest) Transport ¶
func (c *ClientAuthRequest) Transport(endpoint *Endpoint) error
Transport Transport
type ClientKerberos ¶
type ClientKerberos struct { Username string Password string Realm string Hostname string Port int Proto string SPN string KrbConf string KrbCCache string // contains filtered or unexported fields }
func NewClientKerberos ¶
func NewClientKerberos(settings *Settings) *ClientKerberos
func (*ClientKerberos) Post ¶
func (c *ClientKerberos) Post(clt *Client, request *soap.SoapMessage) (string, error)
func (*ClientKerberos) Transport ¶
func (c *ClientKerberos) Transport(endpoint *Endpoint) error
type ClientNTLM ¶
type ClientNTLM struct {
// contains filtered or unexported fields
}
ClientNTLM provides a transport via NTLMv2
func NewClientNTLMWithDial ¶
func NewClientNTLMWithDial(dial func(network, addr string) (net.Conn, error)) *ClientNTLM
NewClientNTLMWithDial NewClientNTLMWithDial
func NewClientNTLMWithProxyFunc ¶
NewClientNTLMWithProxyFunc NewClientNTLMWithProxyFunc
func (ClientNTLM) Post ¶
func (c ClientNTLM) Post(client *Client, request *soap.SoapMessage) (string, error)
Post make post to the winrm soap service (forwarded to clientRequest implementation)
func (*ClientNTLM) Transport ¶
func (c *ClientNTLM) Transport(endpoint *Endpoint) error
Transport creates the wrapped NTLM transport
type Command ¶
type Command struct { Stdin *commandWriter Stdout *commandReader Stderr *commandReader // contains filtered or unexported fields }
Command represents a given command running on a Shell. This structure allows to get access to the various stdout, stderr and stdin pipes.
type Encryption ¶
type Encryption struct {
// contains filtered or unexported fields
}
func NewEncryption ¶
func NewEncryption(protocol string) (*Encryption, error)
Encrypted Message Types When using Encryption, there are three options available
Negotiate/SPNEGO
Kerberos
CredSSP
protocol: The protocol string used for the particular auth protocol
The auth protocol used, will determine the wrapping and unwrapping method plus the protocol string to use. Currently only NTLM is supported
based on the python code from https://pypi.org/project/pywinrm/
see https://github.com/diyan/pywinrm/blob/master/winrm/encryption.py
uses the most excellent NTLM library from https://github.com/bodgit/ntlmssp
func (*Encryption) ParseEncryptedResponse ¶
func (e *Encryption) ParseEncryptedResponse(response *http.Response) ([]byte, error)
Takes in the encrypted response from the server and decrypts it
:param response: The response that needs to be decrytped :return: The unencrypted message from the server
func (*Encryption) Post ¶
func (e *Encryption) Post(client *Client, message *soap.SoapMessage) (string, error)
func (*Encryption) PrepareEncryptedRequest ¶
func (e *Encryption) PrepareEncryptedRequest(client *Client, endpoint string, message []byte) (string, error)
Creates a prepared request to send to the server with an encrypted message and correct headers
:param endpoint: The endpoint/server to prepare requests to :param message: The unencrypted message to send to the server :return: A prepared request that has an decrypted message
func (*Encryption) PrepareRequest ¶
func (e *Encryption) PrepareRequest(client *Client, endpoint string) error
func (*Encryption) Transport ¶
func (e *Encryption) Transport(endpoint *Endpoint) error
type Endpoint ¶
type Endpoint struct { // host name or ip address Host string // port to determine if it's http or https default // winrm ports (http:5985, https:5986).Versions // of winrm can be customized to listen on other ports Port int // set the flag true for https connections HTTPS bool // set the flag true for skipping ssl verifications Insecure bool // if set, used to verify the hostname on the returned certificate TLSServerName string // pointer pem certs, and key CACert []byte // cert auth to intdetify the server cert Key []byte // public key for client auth connections Cert []byte // cert for client auth connections // duration timeout for the underling tcp conn(http/https base protocol) // if the time exceeds the connection is cloded/timeouts Timeout time.Duration }
Endpoint struct holds configurations for the server endpoint
type ExecuteCommandError ¶
func (*ExecuteCommandError) Error ¶
func (e *ExecuteCommandError) Error() string
func (*ExecuteCommandError) Is ¶
func (e *ExecuteCommandError) Is(err error) bool
func (*ExecuteCommandError) Unwrap ¶
func (b *ExecuteCommandError) Unwrap() error
type Parameters ¶
type Parameters struct { Timeout string Locale string EnvelopeSize int TransportDecorator func() Transporter Dial func(network, addr string) (net.Conn, error) }
Parameters struct defines metadata information and http transport config
func NewParameters ¶
func NewParameters(timeout, locale string, envelopeSize int) *Parameters
NewParameters return new struct of type Parameters this struct makes the configuration for the request, size message, etc.
type Settings ¶
type Settings struct { WinRMUsername string WinRMPassword string WinRMHost string WinRMPort int WinRMProto string WinRMInsecure bool KrbRealm string KrbConfig string KrbSpn string KrbCCache string WinRMUseNTLM bool WinRMPassCredentials bool }
Settings holds all the information necessary to configure the provider
type Shell ¶
type Shell struct {
// contains filtered or unexported fields
}
Shell is the local view of a WinRM Shell of a given Client
func (*Shell) Close ¶
Close will terminate this shell. No commands can be issued once the shell is closed.
type Transporter ¶
type Transporter interface { // init request baset on the transport configurations Post(*Client, *soap.SoapMessage) (string, error) Transport(*Endpoint) error }
Transporter does different transporters and init a Post request based on them