From bb3d191efb616b799037828c86c7a2da23772c6f Mon Sep 17 00:00:00 2001 From: Tom Limoncelli Date: Thu, 15 Feb 2024 13:46:32 -0500 Subject: [PATCH] CHORE: Vendor go-powershell (#2837) --- go.mod | 3 +- go.sum | 2 - pkg/powershell/LICENSE | 21 ++++ pkg/powershell/README.md | 110 ++++++++++++++++++ pkg/powershell/backend/local.go | 38 +++++++ pkg/powershell/backend/ssh.go | 69 +++++++++++ pkg/powershell/backend/types.go | 13 +++ pkg/powershell/middleware/session.go | 47 ++++++++ pkg/powershell/middleware/session_config.go | 95 ++++++++++++++++ pkg/powershell/middleware/types.go | 8 ++ pkg/powershell/middleware/utf8.go | 49 ++++++++ pkg/powershell/shell.go | 120 ++++++++++++++++++++ pkg/powershell/utils/quote.go | 9 ++ pkg/powershell/utils/quote_test.go | 28 +++++ pkg/powershell/utils/rand.go | 20 ++++ pkg/powershell/utils/rand_test.go | 16 +++ providers/msdns/powershell.go | 6 +- 17 files changed, 647 insertions(+), 7 deletions(-) create mode 100644 pkg/powershell/LICENSE create mode 100644 pkg/powershell/README.md create mode 100644 pkg/powershell/backend/local.go create mode 100644 pkg/powershell/backend/ssh.go create mode 100644 pkg/powershell/backend/types.go create mode 100644 pkg/powershell/middleware/session.go create mode 100644 pkg/powershell/middleware/session_config.go create mode 100644 pkg/powershell/middleware/types.go create mode 100644 pkg/powershell/middleware/utf8.go create mode 100644 pkg/powershell/shell.go create mode 100644 pkg/powershell/utils/quote.go create mode 100644 pkg/powershell/utils/quote_test.go create mode 100644 pkg/powershell/utils/rand.go create mode 100644 pkg/powershell/utils/rand_test.go diff --git a/go.mod b/go.mod index d9191ce3f..9f41274b8 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,6 @@ require ( github.com/aws/aws-sdk-go-v2/service/route53 v1.37.1 github.com/aws/aws-sdk-go-v2/service/route53domains v1.20.6 github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6 - github.com/bhendo/go-powershell v0.0.0-20190719160123-219e7fb4e41e github.com/billputer/go-namecheap v0.0.0-20210108011502-994a912fb7f9 github.com/centralnicgroup-opensource/rtldev-middleware-go-sdk/v3 v3.5.6 github.com/cloudflare/cloudflare-go v0.87.0 @@ -66,6 +65,7 @@ require ( github.com/fbiville/markdown-table-formatter v0.3.0 github.com/google/go-cmp v0.6.0 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 + github.com/juju/errors v0.0.0-20200330140219-3fe23663418f github.com/kylelemons/godebug v1.1.0 github.com/mattn/go-isatty v0.0.20 github.com/vultr/govultr/v2 v2.17.2 @@ -124,7 +124,6 @@ require ( github.com/hashicorp/go-sockaddr v1.0.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/juju/errors v0.0.0-20200330140219-3fe23663418f // indirect github.com/juju/testing v0.0.0-20210324180055-18c50b0c2098 // indirect github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect github.com/mattn/go-colorable v0.1.13 // indirect diff --git a/go.sum b/go.sum index b3f00e060..712f8c01b 100644 --- a/go.sum +++ b/go.sum @@ -72,8 +72,6 @@ github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6 github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6 h1:4NNbNM2Iq/k57qEu7WfL67UrbPq1uFWxW4qODCohi+0= github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6/go.mod h1:J29hk+f9lJrblVIfiJOtTFk+OblBawmib4uz/VdKzlg= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bhendo/go-powershell v0.0.0-20190719160123-219e7fb4e41e h1:KCjb01YiNoRaJ5c+SbnPLWjVzU9vqRYHg3e5JcN50nM= -github.com/bhendo/go-powershell v0.0.0-20190719160123-219e7fb4e41e/go.mod h1:f7vw6ObmmNcyFQLhZX9eUGBJGpnwTJFDvVjqZxIxHWY= github.com/billputer/go-namecheap v0.0.0-20210108011502-994a912fb7f9 h1:2vQTbEJvFsyd1VefzZ34GUkUD6TkJleYYJh9/25WBE4= github.com/billputer/go-namecheap v0.0.0-20210108011502-994a912fb7f9/go.mod h1:bqqNsI2akL+lLWyApkYY0cxquWPKwEBU0Wd3chi3TEg= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= diff --git a/pkg/powershell/LICENSE b/pkg/powershell/LICENSE new file mode 100644 index 000000000..0e06ff19d --- /dev/null +++ b/pkg/powershell/LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2017, Gorillalabs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +http://www.opensource.org/licenses/MIT diff --git a/pkg/powershell/README.md b/pkg/powershell/README.md new file mode 100644 index 000000000..10c3723fb --- /dev/null +++ b/pkg/powershell/README.md @@ -0,0 +1,110 @@ +# go-powershell + +This package is inspired by [jPowerShell](https://github.com/profesorfalken/jPowerShell) +and allows one to run and remote-control a PowerShell session. Use this if you +don't have a static script that you want to execute, bur rather run dynamic +commands. + +## Installation + + go get github.com/bhendo/go-powershell + +## Usage + +To start a PowerShell shell, you need a backend. Backends take care of starting +and controlling the actual powershell.exe process. In most cases, you will want +to use the Local backend, which just uses ``os/exec`` to start the process. + +```go +package main + +import ( + "fmt" + + ps "github.com/bhendo/go-powershell" + "github.com/bhendo/go-powershell/backend" +) + +func main() { + // choose a backend + back := &backend.Local{} + + // start a local powershell process + shell, err := ps.New(back) + if err != nil { + panic(err) + } + defer shell.Exit() + + // ... and interact with it + stdout, stderr, err := shell.Execute("Get-WmiObject -Class Win32_Processor") + if err != nil { + panic(err) + } + + fmt.Println(stdout) +} +``` + +## Remote Sessions + +You can use an existing PS shell to use PSSession cmdlets to connect to remote +computers. Instead of manually handling that, you can use the Session middleware, +which takes care of authentication. Note that you can still use the "raw" shell +to execute commands on the computer where the powershell host process is running. + +```go +package main + +import ( + "fmt" + + ps "github.com/bhendo/go-powershell" + "github.com/bhendo/go-powershell/backend" + "github.com/bhendo/go-powershell/middleware" +) + +func main() { + // choose a backend + back := &backend.Local{} + + // start a local powershell process + shell, err := ps.New(back) + if err != nil { + panic(err) + } + + // prepare remote session configuration + config := middleware.NewSessionConfig() + config.ComputerName = "remote-pc-1" + + // create a new shell by wrapping the existing one in the session middleware + session, err := middleware.NewSession(shell, config) + if err != nil { + panic(err) + } + defer session.Exit() // will also close the underlying ps shell! + + // everything run via the session is run on the remote machine + stdout, stderr, err = session.Execute("Get-WmiObject -Class Win32_Processor") + if err != nil { + panic(err) + } + + fmt.Println(stdout) +} +``` + +Note that a single shell instance is not safe for concurrent use, as are remote +sessions. You can have as many remote sessions using the same shell as you like, +but you must execute commands serially. If you need concurrency, you can just +spawn multiple PowerShell processes (i.e. call ``.New()`` multiple times). + +Also, note that all commands that you execute are wrapped in special echo +statements to delimit the stdout/stderr streams. After ``.Execute()``ing a command, +you can therefore not access ``$LastExitCode`` anymore and expect meaningful +results. + +## License + +MIT, see LICENSE file. diff --git a/pkg/powershell/backend/local.go b/pkg/powershell/backend/local.go new file mode 100644 index 000000000..429b1fe24 --- /dev/null +++ b/pkg/powershell/backend/local.go @@ -0,0 +1,38 @@ +// Copyright (c) 2017 Gorillalabs. All rights reserved. + +package backend + +import ( + "io" + "os/exec" + + "github.com/juju/errors" +) + +type Local struct{} + +func (b *Local) StartProcess(cmd string, args ...string) (Waiter, io.Writer, io.Reader, io.Reader, error) { + command := exec.Command(cmd, args...) + + stdin, err := command.StdinPipe() + if err != nil { + return nil, nil, nil, nil, errors.Annotate(err, "Could not get hold of the PowerShell's stdin stream") + } + + stdout, err := command.StdoutPipe() + if err != nil { + return nil, nil, nil, nil, errors.Annotate(err, "Could not get hold of the PowerShell's stdout stream") + } + + stderr, err := command.StderrPipe() + if err != nil { + return nil, nil, nil, nil, errors.Annotate(err, "Could not get hold of the PowerShell's stderr stream") + } + + err = command.Start() + if err != nil { + return nil, nil, nil, nil, errors.Annotate(err, "Could not spawn PowerShell process") + } + + return command, stdin, stdout, stderr, nil +} diff --git a/pkg/powershell/backend/ssh.go b/pkg/powershell/backend/ssh.go new file mode 100644 index 000000000..68c918280 --- /dev/null +++ b/pkg/powershell/backend/ssh.go @@ -0,0 +1,69 @@ +// Copyright (c) 2017 Gorillalabs. All rights reserved. + +package backend + +import ( + "fmt" + "io" + "regexp" + "strings" + + "github.com/juju/errors" +) + +// sshSession exists so we don't create a hard dependency on crypto/ssh. +type sshSession interface { + Waiter + + StdinPipe() (io.WriteCloser, error) + StdoutPipe() (io.Reader, error) + StderrPipe() (io.Reader, error) + Start(string) error +} + +type SSH struct { + Session sshSession +} + +func (b *SSH) StartProcess(cmd string, args ...string) (Waiter, io.Writer, io.Reader, io.Reader, error) { + stdin, err := b.Session.StdinPipe() + if err != nil { + return nil, nil, nil, nil, errors.Annotate(err, "Could not get hold of the SSH session's stdin stream") + } + + stdout, err := b.Session.StdoutPipe() + if err != nil { + return nil, nil, nil, nil, errors.Annotate(err, "Could not get hold of the SSH session's stdout stream") + } + + stderr, err := b.Session.StderrPipe() + if err != nil { + return nil, nil, nil, nil, errors.Annotate(err, "Could not get hold of the SSH session's stderr stream") + } + + err = b.Session.Start(b.createCmd(cmd, args)) + if err != nil { + return nil, nil, nil, nil, errors.Annotate(err, "Could not spawn process via SSH") + } + + return b.Session, stdin, stdout, stderr, nil +} + +func (b *SSH) createCmd(cmd string, args []string) string { + parts := []string{cmd} + simple := regexp.MustCompile(`^[a-z0-9_/.~+-]+$`) + + for _, arg := range args { + if !simple.MatchString(arg) { + arg = b.quote(arg) + } + + parts = append(parts, arg) + } + + return strings.Join(parts, " ") +} + +func (b *SSH) quote(s string) string { + return fmt.Sprintf(`"%s"`, s) +} diff --git a/pkg/powershell/backend/types.go b/pkg/powershell/backend/types.go new file mode 100644 index 000000000..505471c12 --- /dev/null +++ b/pkg/powershell/backend/types.go @@ -0,0 +1,13 @@ +// Copyright (c) 2017 Gorillalabs. All rights reserved. + +package backend + +import "io" + +type Waiter interface { + Wait() error +} + +type Starter interface { + StartProcess(cmd string, args ...string) (Waiter, io.Writer, io.Reader, io.Reader, error) +} diff --git a/pkg/powershell/middleware/session.go b/pkg/powershell/middleware/session.go new file mode 100644 index 000000000..d96a2e0cb --- /dev/null +++ b/pkg/powershell/middleware/session.go @@ -0,0 +1,47 @@ +// Copyright (c) 2017 Gorillalabs. All rights reserved. + +package middleware + +import ( + "fmt" + "strings" + + "github.com/StackExchange/dnscontrol/v4/pkg/powershell/utils" + "github.com/juju/errors" +) + +type session struct { + upstream Middleware + name string +} + +func NewSession(upstream Middleware, config *SessionConfig) (Middleware, error) { + asserted, ok := config.Credential.(credential) + if ok { + credentialParamValue, err := asserted.prepare(upstream) + if err != nil { + return nil, errors.Annotate(err, "Could not setup credentials") + } + + config.Credential = credentialParamValue + } + + name := "goSess" + utils.CreateRandomString(8) + args := strings.Join(config.ToArgs(), " ") + + _, _, err := upstream.Execute(fmt.Sprintf("$%s = New-PSSession %s", name, args)) + if err != nil { + return nil, errors.Annotate(err, "Could not create new PSSession") + } + + return &session{upstream, name}, nil +} + +func (s *session) Execute(cmd string) (string, string, error) { + return s.upstream.Execute(fmt.Sprintf("Invoke-Command -Session $%s -Script {%s}", s.name, cmd)) +} + +func (s *session) Exit() { + s.upstream.Execute(fmt.Sprintf("Disconnect-PSSession -Session $%s", s.name)) + s.upstream.Exit() +} diff --git a/pkg/powershell/middleware/session_config.go b/pkg/powershell/middleware/session_config.go new file mode 100644 index 000000000..f9f47421b --- /dev/null +++ b/pkg/powershell/middleware/session_config.go @@ -0,0 +1,95 @@ +// Copyright (c) 2017 Gorillalabs. All rights reserved. + +package middleware + +import ( + "fmt" + "strconv" + + "github.com/StackExchange/dnscontrol/v4/pkg/powershell/utils" + "github.com/juju/errors" +) + +const ( + HTTPPort = 5985 + HTTPSPort = 5986 +) + +type SessionConfig struct { + ComputerName string + AllowRedirection bool + Authentication string + CertificateThumbprint string + Credential interface{} + Port int + UseSSL bool +} + +func NewSessionConfig() *SessionConfig { + return &SessionConfig{} +} + +func (c *SessionConfig) ToArgs() []string { + args := make([]string, 0) + + if c.ComputerName != "" { + args = append(args, "-ComputerName") + args = append(args, utils.QuoteArg(c.ComputerName)) + } + + if c.AllowRedirection { + args = append(args, "-AllowRedirection") + } + + if c.Authentication != "" { + args = append(args, "-Authentication") + args = append(args, utils.QuoteArg(c.Authentication)) + } + + if c.CertificateThumbprint != "" { + args = append(args, "-CertificateThumbprint") + args = append(args, utils.QuoteArg(c.CertificateThumbprint)) + } + + if c.Port > 0 { + args = append(args, "-Port") + args = append(args, strconv.Itoa(c.Port)) + } + + if asserted, ok := c.Credential.(string); ok { + args = append(args, "-Credential") + args = append(args, asserted) // do not quote, as it contains a variable name when using password auth + } + + if c.UseSSL { + args = append(args, "-UseSSL") + } + + return args +} + +type credential interface { + prepare(Middleware) (interface{}, error) +} + +type UserPasswordCredential struct { + Username string + Password string +} + +func (c *UserPasswordCredential) prepare(s Middleware) (interface{}, error) { + name := "goCred" + utils.CreateRandomString(8) + pwname := "goPass" + utils.CreateRandomString(8) + + _, _, err := s.Execute(fmt.Sprintf("$%s = ConvertTo-SecureString -String %s -AsPlainText -Force", pwname, utils.QuoteArg(c.Password))) + if err != nil { + return nil, errors.Annotate(err, "Could not convert password to secure string") + } + + _, _, err = s.Execute(fmt.Sprintf("$%s = New-Object -TypeName 'System.Management.Automation.PSCredential' -ArgumentList %s, $%s", name, utils.QuoteArg(c.Username), pwname)) + if err != nil { + return nil, errors.Annotate(err, "Could not create PSCredential object") + } + + return fmt.Sprintf("$%s", name), nil +} diff --git a/pkg/powershell/middleware/types.go b/pkg/powershell/middleware/types.go new file mode 100644 index 000000000..ce35ffa86 --- /dev/null +++ b/pkg/powershell/middleware/types.go @@ -0,0 +1,8 @@ +// Copyright (c) 2017 Gorillalabs. All rights reserved. + +package middleware + +type Middleware interface { + Execute(cmd string) (string, string, error) + Exit() +} diff --git a/pkg/powershell/middleware/utf8.go b/pkg/powershell/middleware/utf8.go new file mode 100644 index 000000000..3ef940570 --- /dev/null +++ b/pkg/powershell/middleware/utf8.go @@ -0,0 +1,49 @@ +// Copyright (c) 2017 Gorillalabs. All rights reserved. + +package middleware + +import ( + "encoding/base64" + "fmt" + + "github.com/StackExchange/dnscontrol/v4/pkg/powershell/utils" +) + +// utf8 implements a primitive middleware that encodes all outputs +// as base64 to prevent encoding issues between remote PowerShell +// shells and the receiver. Just setting $OutputEncoding does not +// work reliably enough, sadly. +type utf8 struct { + upstream Middleware + wrapper string +} + +func NewUTF8(upstream Middleware) (Middleware, error) { + wrapper := "goUTF8" + utils.CreateRandomString(8) + + _, _, err := upstream.Execute(fmt.Sprintf(`function %s { process { if ($_) { [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($_)) } else { '' } } }`, wrapper)) + + return &utf8{upstream, wrapper}, err +} + +func (u *utf8) Execute(cmd string) (string, string, error) { + // Out-String to concat all lines into a single line, + // Write-Host to prevent line breaks at the "window width" + cmd = fmt.Sprintf(`%s | Out-String | %s | Write-Host`, cmd, u.wrapper) + + stdout, stderr, err := u.upstream.Execute(cmd) + if err != nil { + return stdout, stderr, err + } + + decoded, err := base64.StdEncoding.DecodeString(stdout) + if err != nil { + return stdout, stderr, err + } + + return string(decoded), stderr, nil +} + +func (u *utf8) Exit() { + u.upstream.Exit() +} diff --git a/pkg/powershell/shell.go b/pkg/powershell/shell.go new file mode 100644 index 000000000..2215f596c --- /dev/null +++ b/pkg/powershell/shell.go @@ -0,0 +1,120 @@ +// Copyright (c) 2017 Gorillalabs. All rights reserved. + +package powershell + +import ( + "fmt" + "io" + "strings" + "sync" + + "github.com/StackExchange/dnscontrol/v4/pkg/powershell/backend" + "github.com/StackExchange/dnscontrol/v4/pkg/powershell/utils" + "github.com/juju/errors" +) + +const newline = "\r\n" + +type Shell interface { + Execute(cmd string) (string, string, error) + Exit() +} + +type shell struct { + handle backend.Waiter + stdin io.Writer + stdout io.Reader + stderr io.Reader +} + +func New(backend backend.Starter) (Shell, error) { + handle, stdin, stdout, stderr, err := backend.StartProcess("powershell.exe", "-NoExit", "-Command", "-") + if err != nil { + return nil, err + } + + return &shell{handle, stdin, stdout, stderr}, nil +} + +func (s *shell) Execute(cmd string) (string, string, error) { + if s.handle == nil { + return "", "", errors.Annotate(errors.New(cmd), "Cannot execute commands on closed shells.") + } + + outBoundary := createBoundary() + errBoundary := createBoundary() + + // wrap the command in special markers so we know when to stop reading from the pipes + full := fmt.Sprintf("%s; echo '%s'; [Console]::Error.WriteLine('%s')%s", cmd, outBoundary, errBoundary, newline) + + _, err := s.stdin.Write([]byte(full)) + if err != nil { + return "", "", errors.Annotate(errors.Annotate(err, cmd), "Could not send PowerShell command") + } + + // read stdout and stderr + sout := "" + serr := "" + + waiter := &sync.WaitGroup{} + waiter.Add(2) + + go streamReader(s.stdout, outBoundary, &sout, waiter) + go streamReader(s.stderr, errBoundary, &serr, waiter) + + waiter.Wait() + + if len(serr) > 0 { + return sout, serr, errors.Annotate(errors.New(cmd), serr) + } + + return sout, serr, nil +} + +func (s *shell) Exit() { + s.stdin.Write([]byte("exit" + newline)) + + // if it's possible to close stdin, do so (some backends, like the local one, + // do support it) + closer, ok := s.stdin.(io.Closer) + if ok { + closer.Close() + } + + s.handle.Wait() + + s.handle = nil + s.stdin = nil + s.stdout = nil + s.stderr = nil +} + +func streamReader(stream io.Reader, boundary string, buffer *string, signal *sync.WaitGroup) error { + // read all output until we have found our boundary token + output := "" + bufsize := 64 + marker := boundary + newline + + for { + buf := make([]byte, bufsize) + read, err := stream.Read(buf) + if err != nil { + return err + } + + output = output + string(buf[:read]) + + if strings.HasSuffix(output, marker) { + break + } + } + + *buffer = strings.TrimSuffix(output, marker) + signal.Done() + + return nil +} + +func createBoundary() string { + return "$gorilla" + utils.CreateRandomString(12) + "$" +} diff --git a/pkg/powershell/utils/quote.go b/pkg/powershell/utils/quote.go new file mode 100644 index 000000000..84dd1d38c --- /dev/null +++ b/pkg/powershell/utils/quote.go @@ -0,0 +1,9 @@ +// Copyright (c) 2017 Gorillalabs. All rights reserved. + +package utils + +import "strings" + +func QuoteArg(s string) string { + return "'" + strings.Replace(s, "'", "\"", -1) + "'" +} diff --git a/pkg/powershell/utils/quote_test.go b/pkg/powershell/utils/quote_test.go new file mode 100644 index 000000000..f1c91fba2 --- /dev/null +++ b/pkg/powershell/utils/quote_test.go @@ -0,0 +1,28 @@ +// Copyright (c) 2017 Gorillalabs. All rights reserved. + +package utils + +import "testing" + +func TestQuotingArguments(t *testing.T) { + testcases := [][]string{ + {"", "''"}, + {"test", "'test'"}, + {"two words", "'two words'"}, + {"quo\"ted", "'quo\"ted'"}, + {"quo'ted", "'quo\"ted'"}, + {"quo\\'ted", "'quo\\\"ted'"}, + {"quo\"t'ed", "'quo\"t\"ed'"}, + {"es\\caped", "'es\\caped'"}, + {"es`caped", "'es`caped'"}, + {"es\\`caped", "'es\\`caped'"}, + } + + for i, testcase := range testcases { + quoted := QuoteArg(testcase[0]) + + if quoted != testcase[1] { + t.Errorf("test %02d failed: input '%s', expected %s, actual %s", i+1, testcase[0], testcase[1], quoted) + } + } +} diff --git a/pkg/powershell/utils/rand.go b/pkg/powershell/utils/rand.go new file mode 100644 index 000000000..245243e5a --- /dev/null +++ b/pkg/powershell/utils/rand.go @@ -0,0 +1,20 @@ +// Copyright (c) 2017 Gorillalabs. All rights reserved. + +package utils + +import ( + "crypto/rand" + "encoding/hex" +) + +func CreateRandomString(bytes int) string { + c := bytes + b := make([]byte, c) + + _, err := rand.Read(b) + if err != nil { + panic(err) + } + + return hex.EncodeToString(b) +} diff --git a/pkg/powershell/utils/rand_test.go b/pkg/powershell/utils/rand_test.go new file mode 100644 index 000000000..8000a9b84 --- /dev/null +++ b/pkg/powershell/utils/rand_test.go @@ -0,0 +1,16 @@ +// Copyright (c) 2017 Gorillalabs. All rights reserved. + +package utils + +import "testing" + +func TestRandomStrings(t *testing.T) { + r1 := CreateRandomString(8) + r2 := CreateRandomString(8) + + if r1 == r2 { + t.Error("Failed to create random strings: The two generated strings are identical.") + } else if len(r1) != 16 { + t.Errorf("Expected the random string to contain 16 characters, but got %d.", len(r1)) + } +} diff --git a/providers/msdns/powershell.go b/providers/msdns/powershell.go index 75fef9d45..3666a3424 100644 --- a/providers/msdns/powershell.go +++ b/providers/msdns/powershell.go @@ -8,11 +8,11 @@ import ( "os" "github.com/StackExchange/dnscontrol/v4/models" + ps "github.com/StackExchange/dnscontrol/v4/pkg/powershell" + "github.com/StackExchange/dnscontrol/v4/pkg/powershell/backend" + "github.com/StackExchange/dnscontrol/v4/pkg/powershell/middleware" "github.com/StackExchange/dnscontrol/v4/pkg/printer" "github.com/TomOnTime/utfutil" - ps "github.com/bhendo/go-powershell" - "github.com/bhendo/go-powershell/backend" - "github.com/bhendo/go-powershell/middleware" ) type psHandle struct {