The Artima Developer Community
Sponsored Link

Java Buzz Forum
RPC Over SSH and Domain Sockets

0 replies on 1 page.

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 0 replies on 1 page
Brian McCallister

Posts: 1282
Nickname: frums
Registered: Sep, 2003

Brian McCallister is JustaProgrammer who thinks too much.
RPC Over SSH and Domain Sockets Posted: Feb 8, 2013 4:12 PM
Reply to this message Reply

This post originated from an RSS feed registered with Java Buzz by Brian McCallister.
Original Post: RPC Over SSH and Domain Sockets
Feed Title: Waste of Time
Feed URL: http://kasparov.skife.org/blog/index.rss
Feed Description: A simple waste of time and weblog experiment
Latest Java Buzz Posts
Latest Java Buzz Posts by Brian McCallister
Latest Posts From Waste of Time

Advertisement

I really like using SSH for authentication and authorization when possible – it is very configurable, well understood, and more secure then anything I am likely to design. It is also generally pretty easy to have applications communicate over SSH. A nice model is to have the server listen on a domain socket in a directory with appropriate permissions, and clients connect over ssh and netcat to talk to it.

Logically, on the client it is:

$ ssh server.example.com /usr/bin/nc -U /tmp/foo

And voila, your client (or shell in this case) is connected to the remote domain socket. After finding Jeff Hodges’s wonderful writeup on go.crypto/ssh I sat down to make Go do this internally. It was fun, and pretty straightforward.

The server is just a net/rpc server which listens on a domain socket and responds with a greeting:

package main

import (
	"fmt"
	"log"
	"net"
	"net/rpc"
	"os"
	"os/signal"
	"syscall"
)

// rpc response
type Response struct {
	Greeting string
}

// rpc request
type Request struct {
	Name string
}

// rpc host struct thing
type Greeter struct{}

// our remotely invocable function
func (g *Greeter) Greet(req Request, res *Response) (err error) {
	res.Greeting = fmt.Sprintf("Hello %s", req.Name)
	return
}

// start up rpc listener at path
func ServeAt(path string) (err error) {
	rpc.Register(&Greeter{})

	listener, err := net.Listen("unix", path)
	if err != nil {
		return fmt.Errorf("unable to listen at %s: %s", path, err)
	}

	go rpc.Accept(listener)
	return
}

// ./server /tmp/foo
func main() {
	path := os.Args[1]

	err := ServeAt(path)
	if err != nil {
		log.Fatalf("failed: %s", err)
	}
	defer os.Remove(path)

	// block until we are signalled to quit
	wait()
}

func wait() {
	signals := make(chan os.Signal)
	signal.Notify(signals, syscall.SIGINT, syscall.SIGKILL, syscall.SIGHUP)
	<-signals
}

The client is the fun part. It establishes an SSH connection to the server host, then fires off a Session against netcat, attaches an RPC client to that session, and does its stuff!

package main

import (
	"code.google.com/p/go.crypto/ssh"
	"fmt"
	"io"
	"log"
	"net"
	"net/rpc"
	"os"
	"strings"
)

// RPC response container
type Response struct {
	Greeting string
}

// RPC request container
type Request struct {
	Name string
}

// It would be nice if ssh.Session was an io.ReaderWriter
// proposal submitted :-)
type NetCatSession struct {
	*ssh.Session // define Close()
	writer io.Writer
	reader io.Reader
}

// io.Reader
func (s NetCatSession) Read(p []byte) (n int, err error) {
	return s.reader.Read(p)
}

// io.Writer
func (s NetCatSession) Write(p []byte) (n int, err error) {
	return s.writer.Write(p)
}

// given the established ssh connection, start a session against netcat and
// return a io.ReaderWriterCloser appropriate for rpc.NewClient(...)
func StartNetCat(client *ssh.ClientConn, path string) (rwc *NetCatSession, err error) {
	session, err := client.NewSession()
	if err != nil {
		return
	}

	cmd := fmt.Sprintf("/usr/bin/nc -U %s", path)
	in, err := session.StdinPipe()
	if err != nil {
		return nil, fmt.Errorf("unable to get stdin: %s", err)
	}

	out, err := session.StdoutPipe()
	if err != nil {
		return nil, fmt.Errorf("unable to get stdout: %s", err)
	}

	err = session.Start(cmd)
	if err != nil {
		return nil, fmt.Errorf("unable to start '%s': %s", cmd, err)
	}

	return &NetCatSession{session, in, out}, nil
}


// ./client localhost:/tmp/foo Brian
func main() {
	parts := strings.Split(os.Args[1], ":")
	host := parts[0]
	path := parts[1]
	name := os.Args[2]


	// SSH setup, we assume current username and use the ssh agent
	// for auth
	agent_sock, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK"))
	if err != nil {
		log.Fatalf("sorry, this example requires the ssh agent: %s", err)
	}
	defer agent_sock.Close()

	config := &ssh.ClientConfig{
		User: os.Getenv("USER"),
		Auth: []ssh.ClientAuth{
			ssh.ClientAuthAgent(ssh.NewAgentClient(agent_sock)),
		},
	}
	ssh_client, err := ssh.Dial("tcp", fmt.Sprintf("%s:22", host), config)
	if err != nil {
		log.Fatalf("Failed to dial: %s", err)
	}
	defer ssh_client.Close()


	// Establish sesstion to netcat talking to the domain socket
	s, err := StartNetCat(ssh_client, path)
	if err != nil {
		log.Fatalf("unable to start netcat session: %s", err)
	}


	// now comes the RPC!
	client := rpc.NewClient(s)
	defer client.Close()

	req := &Request{name}
	var res Response

	err = client.Call("Greeter.Greet", req, &res)
	if err != nil {
		log.Fatalf("error in rpc: %s", err)
	}
	fmt.Println(res.Greeting)
}

And there it is! This isn’t exactly library code, but it nicely bundles up how to do it.

I really like using domain sockets and SSH for “operational” stuff. The slight overhead of firing up extra processes on the server, and hopping between tcp and unix sockets doesn’t usually matter, and you get lots of nice well understood and configurable security for your sessions.

In this case, I’m using SSH-as-a-library, in the past I have shelled out to SSH in order to take advantage of client side SSH configuration as well. Which makes the most sense varies, of course :-)

$ ./server /tmp/foo &
[1] 46206
$ ./client localhost:/tmp/foo "brave SSH world"
Hello brave SSH world
$ 

Read: RPC Over SSH and Domain Sockets

Topic: Class in Java and Object oriented programming language - Video Tutorial Previous Topic   Next Topic Topic: Using Google Guava's Ordering API

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use