From b598baa5e30513144853a7700d27a178fe8b6ce5 Mon Sep 17 00:00:00 2001 From: David Campbell Date: Wed, 19 Oct 2016 19:50:35 -0700 Subject: [PATCH 1/3] Use SSH agent when enabled for bastion step --- helper/communicator/step_connect_ssh.go | 31 +++- helper/communicator/step_connect_ssh_test.go | 160 +++++++++++++++++++ 2 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 helper/communicator/step_connect_ssh_test.go diff --git a/helper/communicator/step_connect_ssh.go b/helper/communicator/step_connect_ssh.go index 71a6d1a39..6aa0fda38 100644 --- a/helper/communicator/step_connect_ssh.go +++ b/helper/communicator/step_connect_ssh.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "net" + "os" "strings" "time" @@ -13,6 +14,7 @@ import ( "github.com/mitchellh/packer/communicator/ssh" "github.com/mitchellh/packer/packer" gossh "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/agent" ) // StepConnectSSH is a step that only connects to SSH. @@ -94,6 +96,7 @@ func (s *StepConnectSSH) waitForSSH(state multistep.StateBag, cancel <-chan stru conf, err := sshBastionConfig(s.Config) if err != nil { + log.Printf("[ERROR] Error calling sshBastionConfig: %v", err) return nil, fmt.Errorf("Error configuring bastion: %s", err) } bConf = conf @@ -196,7 +199,15 @@ func (s *StepConnectSSH) waitForSSH(state multistep.StateBag, cancel <-chan stru } func sshBastionConfig(config *Config) (*gossh.ClientConfig, error) { - auth := make([]gossh.AuthMethod, 0, 2) + var auth []gossh.AuthMethod + + if !config.SSHDisableAgent { + log.Printf("[INFO] SSH agent forwarding enabled.") + if sshAgent := sshAgent(); sshAgent != nil { + auth = append(auth, sshAgent) + } + } + if config.SSHBastionPassword != "" { auth = append(auth, gossh.Password(config.SSHBastionPassword), @@ -218,3 +229,21 @@ func sshBastionConfig(config *Config) (*gossh.ClientConfig, error) { Auth: auth, }, nil } + +func sshAgent() gossh.AuthMethod { + socket := os.Getenv("SSH_AUTH_SOCK") + if socket == "" { + log.Println("[DEBUG] Error fetching SSH_AUTH_SOCK.") + return nil + } + + agentConn, err := net.Dial("unix", socket) + if err != nil { + log.Printf("[WARN] net.Dial: %v", err) + return nil + } + + log.Println("[INFO] Using SSH Agent.") + sshAgent := agent.NewClient(agentConn) + return gossh.PublicKeysCallback(sshAgent.Signers) +} diff --git a/helper/communicator/step_connect_ssh_test.go b/helper/communicator/step_connect_ssh_test.go new file mode 100644 index 000000000..89a7eb057 --- /dev/null +++ b/helper/communicator/step_connect_ssh_test.go @@ -0,0 +1,160 @@ +package communicator + +import ( + "bytes" + "net" + "os" + "os/exec" + "path/filepath" + "strconv" + "testing" + + "golang.org/x/crypto/ssh/agent" +) + +// startAgent executes ssh-agent, and returns a Agent interface to it. +func startAgent(t *testing.T) (agent.Agent, func()) { + if testing.Short() { + // ssh-agent is not always available, and the key + // types supported vary by platform. + t.Skip("skipping test due to -short") + } + + bin, err := exec.LookPath("ssh-agent") + if err != nil { + t.Skip("could not find ssh-agent") + } + + cmd := exec.Command(bin, "-s") + out, err := cmd.Output() + if err != nil { + t.Fatalf("cmd.Output: %v", err) + } + + /* Output looks like: + SSH_AUTH_SOCK=/tmp/ssh-P65gpcqArqvH/agent.15541; export SSH_AUTH_SOCK; + SSH_AGENT_PID=15542; export SSH_AGENT_PID; + echo Agent pid 15542; + */ + fields := bytes.Split(out, []byte(";")) + line := bytes.SplitN(fields[0], []byte("="), 2) + line[0] = bytes.TrimLeft(line[0], "\n") + if string(line[0]) != "SSH_AUTH_SOCK" { + t.Fatalf("could not find key SSH_AUTH_SOCK in %q", fields[0]) + } + socket := string(line[1]) + t.Logf("Socket value: %v", socket) + + origSocket := os.Getenv("SSH_AUTH_SOCK") + if err := os.Setenv("SSH_AUTH_SOCK", socket); err != nil { + t.Fatalf("could not set SSH_AUTH_SOCK environment variable: %v", err) + } + + line = bytes.SplitN(fields[2], []byte("="), 2) + line[0] = bytes.TrimLeft(line[0], "\n") + if string(line[0]) != "SSH_AGENT_PID" { + t.Fatalf("could not find key SSH_AGENT_PID in %q", fields[2]) + } + pidStr := line[1] + t.Logf("Agent PID: %v", string(pidStr)) + pid, err := strconv.Atoi(string(pidStr)) + if err != nil { + t.Fatalf("Atoi(%q): %v", pidStr, err) + } + + conn, err := net.Dial("unix", string(socket)) + if err != nil { + t.Fatalf("net.Dial: %v", err) + } + + return agent.NewClient(conn), func() { + proc, _ := os.FindProcess(pid) + if proc != nil { + proc.Kill() + } + + os.Setenv("SSH_AUTH_SOCK", origSocket) + conn.Close() + os.RemoveAll(filepath.Dir(socket)) + } +} + +func TestSSHAgent(t *testing.T) { + _, cleanup := startAgent(t) + defer cleanup() + + if auth := sshAgent(); auth == nil { + t.Error("Want `ssh.AuthMethod`, got `nil`") + } +} + +func TestSSHBastionConfig(t *testing.T) { + pemPath := TestPEM(t) + tests := []struct { + in *Config + errStr string + want int + fn func() func() + }{ + { + in: &Config{SSHDisableAgent: true}, + want: 0, + }, + { + in: &Config{SSHDisableAgent: false}, + want: 0, + fn: func() func() { + _, cleanup := startAgent(t) + os.Unsetenv("SSH_AUTH_SOCK") + return cleanup + }, + }, + { + in: &Config{ + SSHDisableAgent: false, + SSHBastionPassword: "foobar", + SSHBastionPrivateKey: pemPath, + }, + want: 4, + fn: func() func() { + _, cleanup := startAgent(t) + return cleanup + }, + }, + { + in: &Config{ + SSHBastionPrivateKey: pemPath, + }, + want: 0, + errStr: "Failed to read key '" + pemPath + "': no key found", + fn: func() func() { + os.Truncate(pemPath, 0) + return func() { + if err := os.Remove(pemPath); err != nil { + t.Fatalf("os.Remove: %v", err) + } + } + }, + }, + } + + for _, c := range tests { + func() { + if c.fn != nil { + defered := c.fn() + defer defered() + } + bConf, err := sshBastionConfig(c.in) + if err != nil { + if err.Error() != c.errStr { + t.Errorf("want error %v, got %q", c.errStr, err) + } + return + } + + if len(bConf.Auth) != c.want { + t.Errorf("want %v ssh.AuthMethod, got %v ssh.AuthMethod", c.want, len(bConf.Auth)) + } + }() + } +} From 6c7e3b70a960d089d3ccf2590d373542c8140a79 Mon Sep 17 00:00:00 2001 From: David Campbell Date: Thu, 20 Oct 2016 17:59:22 -0700 Subject: [PATCH 2/3] - use xanzy/ssh-agent for windows compatibility --- helper/communicator/step_connect_ssh.go | 13 +- helper/communicator/step_connect_ssh_test.go | 23 +- vendor/github.com/xanzy/ssh-agent/LICENSE | 202 ++++++++++++++++++ vendor/github.com/xanzy/ssh-agent/README.md | 23 ++ .../xanzy/ssh-agent/pageant_windows.go | 146 +++++++++++++ vendor/github.com/xanzy/ssh-agent/sshagent.go | 49 +++++ .../xanzy/ssh-agent/sshagent_windows.go | 80 +++++++ vendor/vendor.json | 5 + 8 files changed, 517 insertions(+), 24 deletions(-) create mode 100644 vendor/github.com/xanzy/ssh-agent/LICENSE create mode 100644 vendor/github.com/xanzy/ssh-agent/README.md create mode 100644 vendor/github.com/xanzy/ssh-agent/pageant_windows.go create mode 100644 vendor/github.com/xanzy/ssh-agent/sshagent.go create mode 100644 vendor/github.com/xanzy/ssh-agent/sshagent_windows.go diff --git a/helper/communicator/step_connect_ssh.go b/helper/communicator/step_connect_ssh.go index 6aa0fda38..2db3fd07a 100644 --- a/helper/communicator/step_connect_ssh.go +++ b/helper/communicator/step_connect_ssh.go @@ -5,7 +5,6 @@ import ( "fmt" "log" "net" - "os" "strings" "time" @@ -13,8 +12,8 @@ import ( commonssh "github.com/mitchellh/packer/common/ssh" "github.com/mitchellh/packer/communicator/ssh" "github.com/mitchellh/packer/packer" + "github.com/xanzy/ssh-agent" gossh "golang.org/x/crypto/ssh" - "golang.org/x/crypto/ssh/agent" ) // StepConnectSSH is a step that only connects to SSH. @@ -231,19 +230,17 @@ func sshBastionConfig(config *Config) (*gossh.ClientConfig, error) { } func sshAgent() gossh.AuthMethod { - socket := os.Getenv("SSH_AUTH_SOCK") - if socket == "" { + if !sshagent.Available() { log.Println("[DEBUG] Error fetching SSH_AUTH_SOCK.") return nil } - agentConn, err := net.Dial("unix", socket) + agent, _, err := sshagent.New() if err != nil { - log.Printf("[WARN] net.Dial: %v", err) + log.Printf("[WARN] sshagent.New: %v", err) return nil } log.Println("[INFO] Using SSH Agent.") - sshAgent := agent.NewClient(agentConn) - return gossh.PublicKeysCallback(sshAgent.Signers) + return gossh.PublicKeysCallback(agent.Signers) } diff --git a/helper/communicator/step_connect_ssh_test.go b/helper/communicator/step_connect_ssh_test.go index 89a7eb057..77b8f1b74 100644 --- a/helper/communicator/step_connect_ssh_test.go +++ b/helper/communicator/step_connect_ssh_test.go @@ -2,22 +2,19 @@ package communicator import ( "bytes" - "net" "os" "os/exec" "path/filepath" "strconv" "testing" - - "golang.org/x/crypto/ssh/agent" ) -// startAgent executes ssh-agent, and returns a Agent interface to it. -func startAgent(t *testing.T) (agent.Agent, func()) { +// startAgent sets ssh-agent environment variables +func startAgent(t *testing.T) func() { if testing.Short() { // ssh-agent is not always available, and the key // types supported vary by platform. - t.Skip("skipping test due to -short") + t.Skip("skipping test due to -short or availability") } bin, err := exec.LookPath("ssh-agent") @@ -62,25 +59,19 @@ func startAgent(t *testing.T) (agent.Agent, func()) { t.Fatalf("Atoi(%q): %v", pidStr, err) } - conn, err := net.Dial("unix", string(socket)) - if err != nil { - t.Fatalf("net.Dial: %v", err) - } - - return agent.NewClient(conn), func() { + return func() { proc, _ := os.FindProcess(pid) if proc != nil { proc.Kill() } os.Setenv("SSH_AUTH_SOCK", origSocket) - conn.Close() os.RemoveAll(filepath.Dir(socket)) } } func TestSSHAgent(t *testing.T) { - _, cleanup := startAgent(t) + cleanup := startAgent(t) defer cleanup() if auth := sshAgent(); auth == nil { @@ -104,7 +95,7 @@ func TestSSHBastionConfig(t *testing.T) { in: &Config{SSHDisableAgent: false}, want: 0, fn: func() func() { - _, cleanup := startAgent(t) + cleanup := startAgent(t) os.Unsetenv("SSH_AUTH_SOCK") return cleanup }, @@ -117,7 +108,7 @@ func TestSSHBastionConfig(t *testing.T) { }, want: 4, fn: func() func() { - _, cleanup := startAgent(t) + cleanup := startAgent(t) return cleanup }, }, diff --git a/vendor/github.com/xanzy/ssh-agent/LICENSE b/vendor/github.com/xanzy/ssh-agent/LICENSE new file mode 100644 index 000000000..8f71f43fe --- /dev/null +++ b/vendor/github.com/xanzy/ssh-agent/LICENSE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/vendor/github.com/xanzy/ssh-agent/README.md b/vendor/github.com/xanzy/ssh-agent/README.md new file mode 100644 index 000000000..d93af40a0 --- /dev/null +++ b/vendor/github.com/xanzy/ssh-agent/README.md @@ -0,0 +1,23 @@ +# ssh-agent + +Create a new [agent.Agent](https://godoc.org/golang.org/x/crypto/ssh/agent#Agent) on any type of OS (so including Windows) from any [Go](https://golang.org) application. + +## Limitations + +When compiled for Windows, it will only support [Pageant](http://the.earth.li/~sgtatham/putty/0.66/htmldoc/Chapter9.html#pageant) as the SSH authentication agent. + +## Credits + +Big thanks to [Давид Мзареулян (David Mzareulyan)](https://github.com/davidmz) for creating the [go-pageant](https://github.com/davidmz/go-pageant) package! + +## Issues + +If you have an issue: report it on the [issue tracker](https://github.com/xanzy/ssh-agent/issues) + +## Author + +Sander van Harmelen () + +## License + +The files `pageant_windows.go` and `sshagent_windows.go` have their own license (see file headers). The rest of this package is licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/vendor/github.com/xanzy/ssh-agent/pageant_windows.go b/vendor/github.com/xanzy/ssh-agent/pageant_windows.go new file mode 100644 index 000000000..3507b0228 --- /dev/null +++ b/vendor/github.com/xanzy/ssh-agent/pageant_windows.go @@ -0,0 +1,146 @@ +// +// Copyright (c) 2014 David Mzareulyan +// +// 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. +// + +// +build windows + +package sshagent + +// see https://github.com/Yasushi/putty/blob/master/windows/winpgntc.c#L155 +// see https://github.com/paramiko/paramiko/blob/master/paramiko/win_pageant.py + +import ( + "encoding/binary" + "errors" + "fmt" + "sync" + . "syscall" + . "unsafe" +) + +// Maximum size of message can be sent to pageant +const MaxMessageLen = 8192 + +var ( + ErrPageantNotFound = errors.New("pageant process not found") + ErrSendMessage = errors.New("error sending message") + + ErrMessageTooLong = errors.New("message too long") + ErrInvalidMessageFormat = errors.New("invalid message format") + ErrResponseTooLong = errors.New("response too long") +) + +const ( + agentCopydataID = 0x804e50ba + wmCopydata = 74 +) + +type copyData struct { + dwData uintptr + cbData uint32 + lpData Pointer +} + +var ( + lock sync.Mutex + + winFindWindow = winAPI("user32.dll", "FindWindowW") + winGetCurrentThreadID = winAPI("kernel32.dll", "GetCurrentThreadId") + winSendMessage = winAPI("user32.dll", "SendMessageW") +) + +func winAPI(dllName, funcName string) func(...uintptr) (uintptr, uintptr, error) { + proc := MustLoadDLL(dllName).MustFindProc(funcName) + return func(a ...uintptr) (uintptr, uintptr, error) { return proc.Call(a...) } +} + +// Available returns true if Pageant is running +func Available() bool { return pageantWindow() != 0 } + +// Query sends message msg to Pageant and returns response or error. +// 'msg' is raw agent request with length prefix +// Response is raw agent response with length prefix +func query(msg []byte) ([]byte, error) { + if len(msg) > MaxMessageLen { + return nil, ErrMessageTooLong + } + + msgLen := binary.BigEndian.Uint32(msg[:4]) + if len(msg) != int(msgLen)+4 { + return nil, ErrInvalidMessageFormat + } + + lock.Lock() + defer lock.Unlock() + + paWin := pageantWindow() + + if paWin == 0 { + return nil, ErrPageantNotFound + } + + thID, _, _ := winGetCurrentThreadID() + mapName := fmt.Sprintf("PageantRequest%08x", thID) + pMapName, _ := UTF16PtrFromString(mapName) + + mmap, err := CreateFileMapping(InvalidHandle, nil, PAGE_READWRITE, 0, MaxMessageLen+4, pMapName) + if err != nil { + return nil, err + } + defer CloseHandle(mmap) + + ptr, err := MapViewOfFile(mmap, FILE_MAP_WRITE, 0, 0, 0) + if err != nil { + return nil, err + } + defer UnmapViewOfFile(ptr) + + mmSlice := (*(*[MaxMessageLen]byte)(Pointer(ptr)))[:] + + copy(mmSlice, msg) + + mapNameBytesZ := append([]byte(mapName), 0) + + cds := copyData{ + dwData: agentCopydataID, + cbData: uint32(len(mapNameBytesZ)), + lpData: Pointer(&(mapNameBytesZ[0])), + } + + resp, _, _ := winSendMessage(paWin, wmCopydata, 0, uintptr(Pointer(&cds))) + + if resp == 0 { + return nil, ErrSendMessage + } + + respLen := binary.BigEndian.Uint32(mmSlice[:4]) + if respLen > MaxMessageLen-4 { + return nil, ErrResponseTooLong + } + + respData := make([]byte, respLen+4) + copy(respData, mmSlice) + + return respData, nil +} + +func pageantWindow() uintptr { + nameP, _ := UTF16PtrFromString("Pageant") + h, _, _ := winFindWindow(uintptr(Pointer(nameP)), uintptr(Pointer(nameP))) + return h +} diff --git a/vendor/github.com/xanzy/ssh-agent/sshagent.go b/vendor/github.com/xanzy/ssh-agent/sshagent.go new file mode 100644 index 000000000..259fea2b6 --- /dev/null +++ b/vendor/github.com/xanzy/ssh-agent/sshagent.go @@ -0,0 +1,49 @@ +// +// Copyright 2015, Sander van Harmelen +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// +build !windows + +package sshagent + +import ( + "errors" + "fmt" + "net" + "os" + + "golang.org/x/crypto/ssh/agent" +) + +// New returns a new agent.Agent that uses a unix socket +func New() (agent.Agent, net.Conn, error) { + if !Available() { + return nil, nil, errors.New("SSH agent requested but SSH_AUTH_SOCK not-specified") + } + + sshAuthSock := os.Getenv("SSH_AUTH_SOCK") + + conn, err := net.Dial("unix", sshAuthSock) + if err != nil { + return nil, nil, fmt.Errorf("Error connecting to SSH_AUTH_SOCK: %v", err) + } + + return agent.NewClient(conn), conn, nil +} + +// Available returns true is a auth socket is defined +func Available() bool { + return os.Getenv("SSH_AUTH_SOCK") != "" +} diff --git a/vendor/github.com/xanzy/ssh-agent/sshagent_windows.go b/vendor/github.com/xanzy/ssh-agent/sshagent_windows.go new file mode 100644 index 000000000..c46710e88 --- /dev/null +++ b/vendor/github.com/xanzy/ssh-agent/sshagent_windows.go @@ -0,0 +1,80 @@ +// +// Copyright (c) 2014 David Mzareulyan +// +// 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. +// + +// +build windows + +package sshagent + +import ( + "errors" + "io" + "net" + "sync" + + "golang.org/x/crypto/ssh/agent" +) + +// New returns a new agent.Agent and the (custom) connection it uses +// to communicate with a running pagent.exe instance (see README.md) +func New() (agent.Agent, net.Conn, error) { + if !Available() { + return nil, nil, errors.New("SSH agent requested but Pageant not running") + } + + return agent.NewClient(&conn{}), nil, nil +} + +type conn struct { + sync.Mutex + buf []byte +} + +func (c *conn) Close() { + c.Lock() + defer c.Unlock() + c.buf = nil +} + +func (c *conn) Write(p []byte) (int, error) { + c.Lock() + defer c.Unlock() + + resp, err := query(p) + if err != nil { + return 0, err + } + + c.buf = append(c.buf, resp...) + + return len(p), nil +} + +func (c *conn) Read(p []byte) (int, error) { + c.Lock() + defer c.Unlock() + + if len(c.buf) == 0 { + return 0, io.EOF + } + + n := copy(p, c.buf) + c.buf = c.buf[n:] + + return n, nil +} diff --git a/vendor/vendor.json b/vendor/vendor.json index fd9ddfa96..1bba6cc09 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -750,6 +750,11 @@ "path": "github.com/xanzy/go-cloudstack/cloudstack", "revision": "7d6a4449b586546246087e96e5c97dbc450f4917", "revisionTime": "2016-09-28T15:38:44Z" + }, + { + "checksumSHA1": "iHiMTBffQvWYlOLu3130JXuQpgQ=", + "path": "github.com/xanzy/ssh-agent", + "revision": "ba9c9e33906f58169366275e3450db66139a31a9" }, { "checksumSHA1": "h+pFYiRHBogczS8/F1NoN3Ata44=", From 23aee04b29cdcabfdb1e6ab0c554460b04d183cb Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Sun, 26 Feb 2017 18:02:58 -0800 Subject: [PATCH 3/3] fix vendor --- vendor/vendor.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/vendor/vendor.json b/vendor/vendor.json index 1bba6cc09..245a23bfe 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -750,11 +750,12 @@ "path": "github.com/xanzy/go-cloudstack/cloudstack", "revision": "7d6a4449b586546246087e96e5c97dbc450f4917", "revisionTime": "2016-09-28T15:38:44Z" - }, - { + }, + { "checksumSHA1": "iHiMTBffQvWYlOLu3130JXuQpgQ=", "path": "github.com/xanzy/ssh-agent", - "revision": "ba9c9e33906f58169366275e3450db66139a31a9" + "revision": "ba9c9e33906f58169366275e3450db66139a31a9", + "revisionTime": "2015-12-15T15:34:51Z" }, { "checksumSHA1": "h+pFYiRHBogczS8/F1NoN3Ata44=",