Skip to content

Commit 772a438

Browse files
author
Brice Figureau
committed
Initial implementation
This resulted from the split of the winrm project in a lib and this winrm-cli tool.
1 parent 369bf07 commit 772a438

File tree

6 files changed

+594
-3
lines changed

6 files changed

+594
-3
lines changed

LICENSE

+2-2
Original file line numberDiff line numberDiff line change
@@ -186,8 +186,8 @@
186186
same "printed page" as the copyright notice for easier
187187
identification within third-party archives.
188188

189-
Copyright {yyyy} {name of copyright owner}
190-
189+
Copyright 2016 Brice Figureau
190+
191191
Licensed under the Apache License, Version 2.0 (the "License");
192192
you may not use this file except in compliance with the License.
193193
You may obtain a copy of the License at

Makefile

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
NO_COLOR=\033[0m
2+
OK_COLOR=\033[32;01m
3+
ERROR_COLOR=\033[31;01m
4+
WARN_COLOR=\033[33;01m
5+
DEPS = $(go list -f '{{range .TestImports}}{{.}} {{end}}' ./... | fgrep -v 'winrm')
6+
7+
all: deps
8+
@mkdir -p bin/
9+
@printf "$(OK_COLOR)==> Building$(NO_COLOR)\n"
10+
@go build -o $(GOPATH)/bin/winrm github.com/masterzen/winrm
11+
12+
deps:
13+
@printf "$(OK_COLOR)==> Installing dependencies$(NO_COLOR)\n"
14+
@go get -d -v ./...
15+
@echo $(DEPS) | xargs -n1 go get -d
16+
17+
updatedeps:
18+
go list ./... | xargs go list -f '{{join .Deps "\n"}}' | grep -v github.com/masterzen/winrm-cli | sort -u | xargs go get -f -u -v
19+
20+
clean:
21+
@rm -rf bin/ pkg/ src/
22+
23+
format:
24+
go fmt ./...
25+
26+
ci: deps
27+
@printf "$(OK_COLOR)==> Testing with Coveralls...$(NO_COLOR)\n"
28+
"$(CURDIR)/scripts/test.sh"
29+
30+
test: deps
31+
@printf "$(OK_COLOR)==> Testing...$(NO_COLOR)\n"
32+
go test ./...
33+
34+
.PHONY: all clean deps format test updatedeps

README.md

+62-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,63 @@
11
# winrm-cli
2-
Command-line tool to remotely execute commands on Windows machines through WinRM
2+
3+
This is a Go command-line executable to execute remote commands on Windows machines through
4+
the use of WinRM/WinRS.
5+
6+
_Note_: this tool 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.
7+
8+
## Contact
9+
10+
- Bugs: https://github.com/masterzen/winrm/issues
11+
12+
13+
## Getting Started
14+
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).
15+
16+
### Preparing the remote Windows machine for Basic authentication
17+
This project supports only basic authentication for local accounts (domain users are not supported). The remote windows system must be prepared for winrm:
18+
19+
_For a PowerShell script to do what is described below in one go, check [Richard Downer's blog](http://www.frontiertown.co.uk/2011/12/overthere-control-windows-from-java/)_
20+
21+
On the remote host, a PowerShell prompt, using the __Run as Administrator__ option and paste in the following lines:
22+
23+
winrm quickconfig
24+
y
25+
winrm set winrm/config/service/Auth '@{Basic="true"}'
26+
winrm set winrm/config/service '@{AllowUnencrypted="true"}'
27+
winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="1024"}'
28+
29+
__N.B.:__ The Windows Firewall needs to be running to run this command. See [Microsoft Knowledge Base article #2004640](http://support.microsoft.com/kb/2004640).
30+
31+
__N.B.:__ Do not disable Negotiate authentication as the windows `winrm` command itself uses this for internal authentication, and you risk getting a system where `winrm` doesn't work anymore.
32+
33+
__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](http://support.microsoft.com/kb/2842230) if you need to run commands that uses more than 150MB of memory.
34+
35+
For more information on WinRM, please refer to <a href="http://msdn.microsoft.com/en-us/library/windows/desktop/aa384426(v=vs.85).aspx">the online documentation at Microsoft's DevCenter</a>.
36+
37+
### Building the winrm-cli executable
38+
39+
You can build winrm-cli from source:
40+
41+
```sh
42+
git clone https://github.com/masterzen/winrm-cli
43+
cd winrm-cli
44+
make
45+
```
46+
47+
This will generate a binary in the base directory called `./winrm`.
48+
49+
_Note_: you need go 1.5+. Please check your installation with
50+
51+
```
52+
go version
53+
```
54+
55+
## Command-line usage
56+
57+
Once built, you can run remote commands like this:
58+
59+
```sh
60+
./winrm -hostname remote.domain.com -username "Administrator" -password "secret" "ipconfig /all"
61+
```
62+
63+

certgen.go

+241
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
package main
2+
3+
import (
4+
"crypto/ecdsa"
5+
"crypto/elliptic"
6+
"crypto/rand"
7+
"crypto/rsa"
8+
"crypto/x509"
9+
"crypto/x509/pkix"
10+
"encoding/asn1"
11+
"encoding/pem"
12+
"fmt"
13+
"math/big"
14+
"time"
15+
)
16+
17+
// KeyType for RSA/ECDSA
18+
type KeyType uint8
19+
20+
// CertConfig struct for
21+
// generating base certificates x509
22+
// for ssl/tls auth
23+
type CertConfig struct {
24+
// Subject identifies the entity associated with the public
25+
// key stored in the subject public key fields.
26+
Subject pkix.Name
27+
// ValidFrom creation date formated as Feb 16 10:04:20 2016
28+
ValidFrom time.Time
29+
// ValidFor the duration that certificate will be valid for
30+
// The validity period for a certificate is the period of time
31+
// form notBefore through notAfter, inclusive.
32+
// Note this fileds will be parsed into the notAfter,notBefore
33+
// x509 cert fields
34+
ValidFor time.Duration
35+
// SizeT can represent the size of bytes RSA cert if you specify in the
36+
SizeT int
37+
// Method to be RSA or it can be ECDSA flag if you specify the ECDSA flag
38+
Method KeyType
39+
}
40+
41+
// definition flags for specific elliptic curves formats
42+
const (
43+
// P224 curve which implements P-224 (see FIPS 186-3, section D.2.2)
44+
P224 = iota
45+
// P256 curve which implements P-256 (see FIPS 186-3, section D.2.3)
46+
P256
47+
// P384 curve which implements P-384 (see FIPS 186-3, section D.2.4)
48+
P384
49+
// P521 curve which implements P-521 (see FIPS 186-3, section D.2.5)
50+
P521
51+
52+
// definition flags for specific methods that the cert will be generated
53+
// RSA method
54+
RSA KeyType = iota
55+
// ECDSA method
56+
ECDSA
57+
)
58+
59+
// OtherName type for asn1 encoding
60+
type OtherName struct {
61+
A string `asn1:"utf8"`
62+
}
63+
64+
// GeneralName type for asn1 encoding
65+
type GeneralName struct {
66+
OID asn1.ObjectIdentifier
67+
OtherName `asn1:"tag:0"`
68+
}
69+
70+
// GeneralNames type for asn1 encoding
71+
type GeneralNames struct {
72+
GeneralName `asn1:"tag:0"`
73+
}
74+
75+
var (
76+
// https://support.microsoft.com/en-us/kb/287547
77+
// szOID_NT_PRINCIPAL_NAME 1.3.6.1.4.1.311.20.2.3
78+
szOID = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 311, 20, 2, 3}
79+
// http://www.umich.edu/~x509/ssleay/asn1-oids.html
80+
// 2 5 29 17 subjectAltName
81+
subjAltName = asn1.ObjectIdentifier{2, 5, 29, 17}
82+
)
83+
84+
// getUPNExtensionValue returns marsheled asn1 encoded info
85+
func getUPNExtensionValue(subject pkix.Name) ([]byte, error) {
86+
// returns the ASN.1 encoding of val
87+
// in addition to the struct tags recognized
88+
// we used:
89+
// utf8 => causes string to be marsheled as ASN.1, UTF8 strings
90+
// tag:x => specifies the ASN.1 tag number; imples ASN.1 CONTEXT SPECIFIC
91+
return asn1.Marshal(GeneralNames{
92+
GeneralName: GeneralName{
93+
// init our ASN.1 object identifier
94+
OID: szOID,
95+
OtherName: OtherName{
96+
A: subject.CommonName,
97+
},
98+
},
99+
})
100+
}
101+
102+
// NewCert generates a new x509 certificates based on the CertConfig passed
103+
func NewCert(config CertConfig) (string, string, error) {
104+
var (
105+
err error
106+
notAfter time.Time
107+
notBefore time.Time
108+
// PrivateKey that can be:
109+
// type e.g *rsa.PrivateKey
110+
// or *ecdsa.PrivateKey
111+
PrivateKey interface{}
112+
)
113+
// parse the time vars from the config into notAfter and notBefore
114+
notBefore = config.ValidFrom
115+
notAfter = notBefore.Add(config.ValidFor)
116+
117+
// choose the method to generate the certificate
118+
switch config.Method {
119+
case RSA:
120+
PrivateKey, err = rsa.GenerateKey(rand.Reader, config.SizeT)
121+
if err != nil {
122+
return "", "", fmt.Errorf("Can't generate rsa private key")
123+
}
124+
125+
// if the ECDSA byte is set
126+
case ECDSA:
127+
PrivateKey, err = genKeyEcdsa(config.SizeT)
128+
if err != nil {
129+
return "", "", fmt.Errorf("Can't generate ecdsa private key")
130+
}
131+
default:
132+
return "", "", fmt.Errorf("Unknown method type to use")
133+
}
134+
135+
// get asn1 encoded info of the subject pkix
136+
value, err := getUPNExtensionValue(config.Subject)
137+
if err != nil {
138+
return "", "", fmt.Errorf("Can't marshal asn1 encoded")
139+
}
140+
141+
// A serial number can be up to 20 octets in size.
142+
// https://tools.ietf.org/html/rfc5280#section-4.1.2.2
143+
serialNum, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 8*20))
144+
if err != nil {
145+
return "", "", fmt.Errorf("Failed to generate serial number: %s", err.Error())
146+
}
147+
148+
// make a new x509 temaplte certificate
149+
// entry point for the generation process
150+
template := x509.Certificate{
151+
SerialNumber: serialNum,
152+
Subject: config.Subject,
153+
// when extenstions are used, as expected in this profile,
154+
// versions MUST be 3(value is 2).
155+
Version: 2,
156+
NotBefore: config.ValidFrom,
157+
NotAfter: notAfter,
158+
KeyUsage: x509.KeyUsageKeyEncipherment,
159+
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
160+
ExtraExtensions: []pkix.Extension{
161+
{
162+
Id: subjAltName,
163+
Critical: false,
164+
Value: value,
165+
},
166+
},
167+
}
168+
169+
cert, err := x509.CreateCertificate(rand.Reader, &template, &template, getPublicKey(PrivateKey), PrivateKey)
170+
if err != nil {
171+
return "", "", fmt.Errorf("Failed to generate cert")
172+
}
173+
174+
certPem := pem.EncodeToMemory(&pem.Block{
175+
Type: "CERTIFICATE",
176+
Bytes: cert,
177+
})
178+
179+
privPem, err := exportPrivKeyToPem(PrivateKey)
180+
if err != nil {
181+
return "", "", fmt.Errorf("Failed to export priv key")
182+
}
183+
184+
return string(certPem), string(privPem), nil
185+
}
186+
187+
// getPublicKey fetch public key with assertion
188+
func getPublicKey(p interface{}) interface{} {
189+
switch t := p.(type) {
190+
case *rsa.PrivateKey:
191+
return t.Public()
192+
case *ecdsa.PrivateKey:
193+
return t.Public()
194+
default:
195+
return nil
196+
}
197+
}
198+
199+
// choose the format for the key and generate it
200+
func genKeyEcdsa(szT int) (*ecdsa.PrivateKey, error) {
201+
switch szT {
202+
case P224:
203+
return ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
204+
case P256:
205+
return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
206+
case P384:
207+
return ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
208+
case P521:
209+
return ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
210+
default:
211+
return nil, fmt.Errorf("Can't generate private key, please specify a format")
212+
}
213+
214+
}
215+
216+
// ExportPrivKeyToPem exports private key from the previous generation
217+
func exportPrivKeyToPem(key interface{}) ([]byte, error) {
218+
219+
var pemBlock *pem.Block
220+
221+
switch k := key.(type) {
222+
case *rsa.PrivateKey:
223+
pemBlock = &pem.Block{
224+
Type: "RSA PRIVATE KEY",
225+
Bytes: x509.MarshalPKCS1PrivateKey(k),
226+
}
227+
case *ecdsa.PrivateKey:
228+
b, err := x509.MarshalECPrivateKey(k)
229+
if err != nil {
230+
return nil, fmt.Errorf("Unable to export ecdsa private key")
231+
}
232+
pemBlock = &pem.Block{
233+
Type: "EC PRIVATE KEY",
234+
Bytes: b,
235+
}
236+
default:
237+
return nil, fmt.Errorf("The private key is not RSA or ECDSA")
238+
}
239+
240+
return pem.EncodeToMemory(pemBlock), nil
241+
}

0 commit comments

Comments
 (0)