rex

package module
v0.0.0-...-66ba595 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 6, 2020 License: MIT Imports: 4 Imported by: 0

README

Rex: Remote process execution service

TODO

  • Command line parsing and error handling is tightly knitted with the code. which might prevent good error reporting. Also makes it non-trivial to keep cli documentation in sync with code. Given some more time, I would have cleaned this up to separate the CLI function implementations away from CLI syntax (and possibly semantics) descriptions. (the cobra and viper combo is my favorite solution).
  • If serializing/deserializing error chains is useful and keeping it would be beneficial, it has to be clear what it does with grpc status codes and errors that are generated in the grpc layer itself.
  • Authz: Using grpc's authorization engine or any well known authorization scheme would have made things less arbitrary and spontaneous.
  • Use of UUIDs (vs strings) for ID objects is almost arbitrary across the code. It would be better to completely get rid of the UUID-ness in the API descriptions as an implementation detail and encapsulate all of its usage in localexec. Everyone else will treat unique IDs as unique strings without any certain conditions.
  • A bit low level, but in retrospect rex.ProcessInfo being treated as a value type (as opposed to pointer type) across API boundaries was a mistake. Not only there is not enough reason to treat it as a value type, but also it's nullability is quite desired across the APIs.
  • Also low level: creating global structs (rex.ProcessInfo) must be done with more care. Currently no API is safe against sudden change to addition of a field to rex.ProcessInfo. The behavior is not unnatural as the new field will simply be unsupported across the system, but still it would have been nice if we could have a compile time trap for this. Maybe NewProcessInfo?

Build

Install gRPC toolkit for go. Afterwards you can build the client and server binaries:

$ make rex rexd

Visit scripts/README.md to generate certificates. You can also use pre-generated certificates in ./fixtures/tls/{client,server,ca}.

Run

You can save the clients' UUIDs for future reference:

CL1_ID="$(openssl x509 -subject -in fixtures/tls/client/1.pem -noout -dates | grep CN | grep -o '[^ ]*$')"
CL2_ID="$(openssl x509 -subject -in fixtures/tls/client/2.pem -noout -dates | grep CN | grep -o '[^ ]*$')"

Next, run the server, passing the ca certificate, server key pair, plus the required policies:

$ ./rexd \
    -ca fixtures/tls/ca/ca.crt -cert fixtures/tls/server/1.pem -key fixtures/tls/server/1.key \
    -datadir data \
    -policy '{"Principal": "*", "Action": "*", "Effect": "Allow"}' \
    -policy '{"Principal": "'$CL2_ID'", "Action": "/Rex/ListProcessInfo", "Effect": "Deny"}'

The -datadir flag specifies the directory that rex will use to store process stdout/stderr. The -policy flag can be passed multiple times. The former policy allows all API calls by all users, otherwise no user is authorized to access any API. The latter disallows a user with UUID equal to $CL2_ID from calling /Rex/ListProcessInfo.

Then on another terminal, first set CL1_ARGS and CL2_ARGS to contain the TLS-related arguments for the client:

CL1_ARGS="-ca fixtures/tls/ca/ca.crt -cert fixtures/tls/client/1.pem -key fixtures/tls/client/1.key"
CL2_ARGS="-ca fixtures/tls/ca/ca.crt -cert fixtures/tls/client/2.pem -key fixtures/tls/client/2.key"

Then run

$ ./rex $CL1_ARGS exec $YOUR_COMMAND_HERE

For example:

$ ./rex $CL1_ARGS exec touch some_file

Verify the results (on the same directory that you ran ./rexd from):

$ ls some_file

Executing a nonexistent file, which allows the client to catch ErrNotFound:

$ ./rex $CL1_ARGS exec nonexistent-binary

Executing a file without the execute permission:

$ ./rex $CL1_ARGS exec ./rex.go

Executing a command by client 2 which will likely write to both stdout and stderr:

$ TASK_ID=$(./rex $CL2_ARGS exec find / -maxdepth 3 2>/dev/null | grep \\-)

And to peek at the results:

$ ./rex $CL2_ARGS $TASK_ID stdout
$ ./rex $CL2_ARGS $TASK_ID stderr

And to make sure clients cannot access each others' resources

$ ./rex $CL2_ARGS $TASK_ID stderr

To get a list of processes (access is not limited to owned processes of the current user for demonstration purposes):

$ ./rex $CL1_ARGS ps

To verify that client with UUID $CL2_ID is not allowed to call /Rex/ListProcessInfo/:

$ ./rex $CL2_ARGS ps

To read the output of a process while it is (probably) still writing to it:

$ ./rex $CL2_ARGS read $(./rex $CL2_ARGS exec find / -maxdepth 6 2>/dev/null | grep \\-) stdout

To send check the status of a process and send SIGINT to it while it's running:

$ TASK_ID=$(./rex $CL2_ARGS exec sleep 100 2>/dev/null | grep \\-)
$ ./rex $CL2_ARGS get $TASK_ID
$ ./rex $CL2_ARGS kill $TASK_ID

To verify that an error is received if a signal is sent to a process that is not running:

$ ./rex $CL2_ARGS get $TASK_ID
$ ./rex $CL2_ARGS kill $TASK_ID

An optional timeout (milliseconds) argument can be passed to the cli:

$ TASK_ID=$(./rex $CL2_ARGS exec find / | grep \\-)
$ ./rex $CL2_ARGS -timeout 1000 read $TASK_ID stdout
$ ./rex $CL2_ARGS kill $TASK_ID

To run tests:

$ make test

To show coverage:

$ make coverage

Design

Design document

License

MIT

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrNotImplemented is returned by any of the API implementations
	// that are not yet implemented.
	ErrNotImplemented = errors.New("not implemented")

	// ErrTLSCredentials indicates that the TLS credentials of the peer
	// could not be extracted.
	ErrTLSCredentials = errors.New("unable to read tls credentials")

	// ErrAccessDenied is returned when a user attempts to call an API but is
	// unauthorized to do so
	ErrAccessDenied = errors.New("access denied")

	// ErrUnauthenticated is returned whenever the received request does not
	// contain information to prove authentication of the caller.
	ErrUnauthenticated = errors.New("unauthenticated")

	// ErrNotFound is returned when a requested resource is not found
	ErrNotFound = errors.New("not found")

	// ErrInvalidArgument is when an invalid arugment is given to a function
	ErrInvalidArgument = errors.New("not found")
)

Functions

func UserIDFromContext

func UserIDFromContext(ctx context.Context) (string, bool)

UserIDFromContext gets the unique identifier of the API user. Returns false as the second argument if no such key is found in the context.

func WithUserID

func WithUserID(ctx context.Context, userID string) context.Context

WithUserID adds the supplied user ID to the given context and returns the resulting context.

Types

type OutputStream

type OutputStream int

OutputStream specifies either stdout or stderr of a process

const (
	// StdoutStream specifies stdout of a process
	StdoutStream OutputStream = 0x1
	// StderrStream specifies stderr of a process
	StderrStream OutputStream = 0x2
)

type ProcessInfo

type ProcessInfo struct {
	// ID is the unique identifier of a process.
	ID uuid.UUID
	// PID is the pid of the process in the OS. Can be recycled by the OS
	// after the process exits.
	PID int
	// ExitCode will hold the exit code of the process after it exits. It is
	// undefined if Running=true.
	ExitCode int
	// Running specifies whether or not the process is currently running.
	Running bool
	// Path is the address to the executable corresponding to the process.
	Path string
	// Args is the list of the command line arguments that were passed to the
	// process upon creation.
	Args []string
	// OwnerID is the unique identifier of the owner of the process.
	OwnerID uuid.UUID
	// Create is the point in time (UTC) at which the process was created.
	Create time.Time
	// Exit is the point in time (UTC) at which the process exited. It is
	// undefined if Running=true.
	Exit time.Time
}

ProcessInfo contains various informations about a process.

type Service

type Service interface {
	// Exec executes a given executable with the supplied args.
	Exec(ctx context.Context, path string, args ...string) (uuid.UUID, error)

	// ListProcessInfo returns a list containing ProcessInfo objects, one
	// for each process previously Exec'd on this server.
	ListProcessInfo(ctx context.Context) ([]ProcessInfo, error)

	// GetProcessInfo returns a ProcessInfo object corresponding to a
	// the processID that is provided
	GetProcessInfo(ctx context.Context, processID uuid.UUID) (ProcessInfo, error)

	// Kill sends the specified signal to the specified process
	Kill(ctx context.Context, processID uuid.UUID, signal int) error

	// Read returns the content of the stdout or the stderr of a process
	Read(ctx context.Context, processID uuid.UUID, target OutputStream) ([]byte, error)
}

Service defines the Rex interface within Go.

Directories

Path Synopsis
cmd
rex

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL