{"id":13788133,"url":"https://github.com/Snowflake-Labs/sansshell","last_synced_at":"2025-05-12T02:32:54.923Z","repository":{"id":37078256,"uuid":"411709740","full_name":"Snowflake-Labs/sansshell","owner":"Snowflake-Labs","description":"A non-interactive daemon for host management","archived":false,"fork":false,"pushed_at":"2024-11-15T13:55:34.000Z","size":21589,"stargazers_count":97,"open_issues_count":9,"forks_count":12,"subscribers_count":6,"default_branch":"main","last_synced_at":"2024-11-15T14:33:57.672Z","etag":null,"topics":["administration","automation","go","reliability","security","unshelled"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Snowflake-Labs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-09-29T14:35:03.000Z","updated_at":"2024-11-12T14:23:22.000Z","dependencies_parsed_at":"2023-12-22T03:56:05.699Z","dependency_job_id":"53559b4b-8673-4e44-9328-00f7b62097a4","html_url":"https://github.com/Snowflake-Labs/sansshell","commit_stats":{"total_commits":447,"total_committers":25,"mean_commits":17.88,"dds":0.6845637583892618,"last_synced_commit":"7370745866585db1a44e1c5d6486fc8a26ca9626"},"previous_names":[],"tags_count":100,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Snowflake-Labs%2Fsansshell","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Snowflake-Labs%2Fsansshell/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Snowflake-Labs%2Fsansshell/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Snowflake-Labs%2Fsansshell/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Snowflake-Labs","download_url":"https://codeload.github.com/Snowflake-Labs/sansshell/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225116917,"owners_count":17423309,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["administration","automation","go","reliability","security","unshelled"],"created_at":"2024-08-03T21:00:37.293Z","updated_at":"2025-05-12T02:32:54.902Z","avatar_url":"https://github.com/Snowflake-Labs.png","language":"Go","funding_links":[],"categories":["Other Usecases"],"sub_categories":["Testing Blogs and Articles"],"readme":"# SansShell\n\n[![Build Status](https://github.com/Snowflake-Labs/sansshell/workflows/Build%20and%20Test/badge.svg?branch=main)](https://github.com/Snowflake-Labs/sansshell/actions?query=workflow%3A%22Build+and+Test%22)\n[![License](https://img.shields.io/:license-Apache%202-brightgreen.svg)](https://github.com/Snowflake-Labs/sansshell/blob/main/LICENSE)\n[![Go Reference](https://pkg.go.dev/badge/github.com/Snowflake-Labs/sansshell.svg)](https://pkg.go.dev/github.com/Snowflake-Labs/sansshell)\n[![Report Card](https://goreportcard.com/badge/github.com/Snowflake-Labs/sansshell)](https://goreportcard.com/report/github.com/Snowflake-Labs/sansshell)\n\nA non-interactive daemon for host management\n\n```mermaid\nflowchart LR;\n\nsubgraph sanssh [\"sansshell client (sanssh)\"]\n    cli;\n    client;\n    subgraph client modules\n      package([package]);\n      file([file]);\n      exec([exec]);\n    end\n    cli --\u003e package --\u003e client;\n    cli --\u003e file --\u003e client;\n    cli --\u003e exec --\u003e client;\nend\nsubgraph proxy [\"proxy (optional)\"]\n    proxy_server[proxy-server];\n    opa_policy[(opa policy)];\n    proxy_server --\u003e opa_policy --\u003e proxy_server\nend\nsubgraph sansshell server [\"sansshell server (on each host)\"]\n    server[sansshell-server];\n    host_apis;\n    s_opa_policy[(opa policy)];\n    subgraph service modules\n      s_package([package]);\n      s_file([file]);\n      s_exec([exec]);\n    end\n    server --\u003e s_package --\u003e host_apis;\n    server --\u003e s_file --\u003e host_apis;\n    server --\u003e s_exec --\u003e host_apis;\n    server --\u003e s_opa_policy --\u003e server\nend\nuser{user};\nuser --\u003e cli;\nclient --\"gRPC (mTLS)\"--\u003e proxy_server\nproxy_server --\"grpc (mTLS)\"---\u003e server\n```\n\nSansShell is primarily a gRPC server with a variety of options for localhost\ndebugging and management. Its goal is to replace the need to use an\ninteractive shell for emergency debugging and recovery with a much safer\ninterface. Each authorized action can be evaluated against an OPA policy,\naudited in advance or after the fact, and is ideally deterministic (for a given\nstate of the local machine).\n\nsanssh is a simple CLI with a friendly API for dumping debugging state and\ninteracting with a remote machine. It also includes a set of convenient but\nperhaps-less-friendly subcommands to address the raw SansShell API endpoints.\n\n## Getting Started\n\nHow to set up, build and run locally for testing. All commands are relative to\nthe project root directory.\n\nBuilding SansShell requires a recent version of Go (check the go.mod file for\nthe current version).\n\n### Build and run\n\nYou need to populate ~/.sansshell with certificates before running.\n\n```\n$ cp -r auth/mtls/testdata ~/.sansshell\n```\n\nThen you can build and run the server, in separate terminal windows:\n\n```\n$ go run ./cmd/sansshell-server\n$ go run ./cmd/sanssh --targets=localhost file read /etc/hosts\n```\n\nYou can also run the proxy to try the full flow:\n\n```\n$ go run ./cmd/sansshell-server\n$ go run ./cmd/proxy-server\n$ go run ./cmd/sanssh --proxy=localhost:50043 --targets=localhost:50042 file read /etc/hosts\n```\n\nMinimal debugging UIs are available at http://localhost:50044 for the server and http://localhost:50046 for the proxy by default.\n\n### Environment setup : protoc\n\nWhen making any change to the protocol buffers, you'll also need the protocol\nbuffer compiler (`protoc`) (version 3 or above) as well as the protoc plugins\nfor Go and Go-GRPC\n\nOn MacOS, the protocol buffer can be installed via homebrew using\n\n```\nbrew install protobuf\n```\n\nOn Linux, protoc can be installed using either the OS package manager, or by\ndirectly installing a release version from the [protocol buffers github][1]\n\n### Environment setup : protoc plugins\n\nOn any platform, once protoc has been installed, you can install the required\ncode generation plugins using `go install`.\n\n```\n$ go install google.golang.org/protobuf/cmd/protoc-gen-go\n$ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc\n$ go install github.com/Snowflake-Labs/sansshell/proxy/protoc-gen-go-grpcproxy\n```\n\nNote that, you'll need to make certain that your `PATH` includes the gobinary\ndirectory (either the value of `$GOBIN`, or, if unset, `$HOME/go/bin`)\n\nThe `tools.go` file contains helpful `go generate` directives which will\ndo this for you, as well as re-generating the service proto files.\n\n```\n$ go generate tools.go\n```\n\n### Dev Environment setup\n#### Required tools\n- [pre-commit 3.8.0+](https://pre-commit.com/index.html)\n- [golangci-lint 1.59.1+](https://golangci-lint.run/welcome/install/#local-installation)\n\nConfiguration:\n- Set up git pre-commit hooks\n```bash\npre-commit install\n```\n\n### Creating your own certificates\n\nAs an alternative to copying auth/mtls/testdata, you can create your own example mTLS certs. See the\n[mtls testdata readme](/auth/mtls/testdata/README.md) for steps.\n\n### Debugging\n\nReflection is included in the RPC servers (proxy and sansshell-server)\nallowing for the use of [grpc_cli](https://github.com/grpc/grpc/blob/master/doc/command_line_tool.md).\n\nIf you are using the certificates from above in ~/.sansshell invoking\ngrpc_cli requires some additional flags for local testing:\n\n```\n$ GRPC_DEFAULT_SSL_ROOTS_FILE_PATH=$HOME/.sansshell/root.pem grpc_cli \\\n  --ssl_client_key=$HOME/.sansshell/client.key --ssl_client_cert=$HOME/.sansshell/client.pem \\\n  --ssl_target=127.0.0.1 --channel_creds_type=ssl ls 127.0.0.1:50043\n```\n\nNOTE: This connects to the proxy. Change to 50042 if you want to connect to the sansshell-server.\n\n### Testing\nTo run unit tests, run the following command:\n```bash\ngo test ./...\n```\n\nTo run integration tests, run the following command:\n```bash\n# Run go integration tests\nINTEGRATION_TEST=yes go test -run \"^TestIntegration.*$\" ./...\n\n# Run bash integration tests\n./test/integration.sh\n```\n\n#### Integration testing\nTo implement integration tests, you need to:\n- Create a new test file name satisfy pattern `\u003cfile-name\u003e_integration_test.go`\n- Name test functions satisfy pattern `TestIntegration\u003cFunctionName\u003e`\n- Add check to skip tests when unit test is running:\n```go\nif os.Getenv(\"INTEGRATION_TEST\") == \"\" {\n    t.Skip(\"skipping integration test\")\n}\n```\n\n## A tour of the codebase\n\nSansShell is composed of 5 primary concepts:\n\n1.  A series of services, which live in the `services/` directory.\n1.  A server which wraps these services into a local host agent.\n1.  A proxy server which can be used as an entry point to processing sansshell\n    RPCs by validating policy and then doing fanout to 1..N actual\n    sansshell servers. This can be done as a one to many RPC where\n    a single incoming RPC is replicated to N backend hosts in one RPC call.\n1.  A reference server binary, which includes all of the services.\n1.  A CLI, which serves as the reference implementation of how to use the\n    services via the agent.\n\n### Services\n\nServices implement at least one gRPC API endpoint, and expose it by calling\n`RegisterSansShellService` from `init()`. The goal is to allow custom\nimplementations of the SansShell Server to easily import services they wish to\nuse, and have zero overhead or risk from services they do not import at compile\ntime.\n\n[Here](/docs/services-architecture.md) you could read more about services architecture.\n\n#### List of available Services\n\n1. Ansible: Run a local ansible playbook and return output\n1. Execute: Execute a command\n1. HealthCheck\n1. File operations: Read, Write, Stat, Sum, rm/rmdir, chmod/chown/chgrp\n   and immutable operations (if OS supported).\n1. Package operations: Install, Upgrade, List, Repolist\n1. Process operations: List, Get stacks (native or Java), Get dumps (core or Java heap)\n1. MPA operations: Multi party authorization for commands\n1. [Network](./services/network):\n   1. [TCP-Check](./services/network/README.md#sanssh-network-tcp-check) - Check if a TCP port is open on a remote host\n1. Service operations: List, Status, Start/stop/restart\n\nTODO: Document service/.../client expectations.\n\n#### Services API versioning and OPA policy\n\nIn most cases, services APIs evolve in a fully-backward compatible model,\nwhere adding new parameters or behaviors do not cause unintentional\nside-effects on authz decisions made by OPA policy.\n\nNow consider a localfile read which accepts a path to a file, for example\n`/tmp/test.txt`. If we extend this service to allow reading all files\nin a particular directory (through read request with `/tmp/*` as argument)\nwe may end up allowing to read `/tmp/secret` file which could be explicitly\ndenied in the OPA policy.\n\nTo allow extensions of Sansshell services functions in a safe way we introduced\na notion of `API version` which follows https://semver.org/. A MAJOR version will\nbe changed each time we add a backward-incompatible change to Sansshell services.\n\nDefault version supported by Sanasshell server is set to `1.0.0`, in order to use\nfeatures of higher API version you should audit your OPA policy to check if there\nare no unintentional side-effects of allowing new Sansshell features.\n\n#### List of current API versions\n\n  - `1.0.0` -- current snapshot of Sansshell API as of\n        https://github.com/Snowflake-Labs/sansshell/tree/v1.40.4.\n  - `2.0.0` -- allow to read a contents of whole directory  by specifying a trailing\n        wildcard, for example `localfile read /tmp/*`.\n\n### The Server class\n\nMost of the logic of instantiating a local SansShell server lives in the\n`server` directory. This instantiates a gRPC server, registers the imported\nservices with that server, and constraints them with the supplied OPA policy.\n\n### The reference Proxy Server binary\n\nThere is a reference implementation of a SansShell Proxy Server in\n`cmd/proxy-server`, which should be suitable as-written for many use cases.\nIt's intentionally kept relatively short, so that it can be copied to another\nrepository and customized by adjusting only the imported services.\n\n### The reference Server binary\n\nThere is a reference implementation of a SansShell Server in\n`cmd/sansshell-server`, which should be suitable as-written for some use cases.\nIt's intentionally kept relatively short, so that it can be copied to another\nrepository and customized by adjusting only the imported services.\n\n### The reference CLI client\n\nThere is a reference implementation of a SansShell CLI Client in\n`cmd/sanssh`. It provides raw access to each gRPC endpoint, as well\nas a way to implement \"convenience\" commands which chain together a series of\nactions.\n\nIt also demonstrates how to set up command line completion. To use this, set\nthe appropriate line in your shell configuration.\n\n```shell\n# In .bashrc\ncomplete -C /path/to/sanssh -o dirnames sanssh\n# Or in .zshrc\nautoload -Uz compinit \u0026\u0026 compinit\nautoload -U +X bashcompinit \u0026\u0026 bashcompinit\ncomplete -C /path/to/sanssh -o dirnames sanssh\n```\n\n## Multi party authorization\n\nMPA, or [multi party authorization](https://en.wikipedia.org/wiki/Multi-party_authorization),\nallows guarding sensitive commands behind additional approval. SansShell\nsupports writing authorization policies that only pass when a command is\napproved by additional entities beyond the caller. See\n[services/mpa/README.md](/services/mpa/README.md) for details on\nimplementation and usage.\n\nTo try this out in the reference client, run the following commands in parallel\nin separate terminals. This will run a server that accepts any command from a\nproxy and a proxy that allows MPA requests from the \"sanssh\" user when approved by the \"approver\" user.\n\n```bash\n# Start the server\ngo run ./cmd/sansshell-server -server-cert ./auth/mtls/testdata/leaf.pem -server-key ./auth/mtls/testdata/leaf.key\n# Start the proxy\ngo run ./cmd/proxy-server -client-cert ./services/mpa/testdata/proxy.pem -client-key ./services/mpa/testdata/proxy.key -server-cert ./services/mpa/testdata/proxy.pem -server-key ./services/mpa/testdata/proxy.key\n# Run a command gated on MPA\ngo run ./cmd/sanssh -client-cert ./auth/mtls/testdata/client.pem -client-key ./auth/mtls/testdata/client.key -mpa -proxy localhost -targets localhost exec run /bin/echo hello world\n# Approve the command above\ngo run ./cmd/sanssh -client-cert ./services/mpa/testdata/approver.pem -client-key ./services/mpa/testdata/approver.key -proxy localhost -targets localhost mpa approve 53feec22-5447f403-c0e0a419\n```\n\n## Extending SansShell\n\nSansShell is built on a principle of \"Don't pay for what you don't use\". This\nis advantageous in both minimizing the resources of SansShell server (binary\nsize, memory footprint, etc) as well as reducing the security risk of running\nit. To accomplish that, all of the SansShell services are independent modules,\nwhich can be optionally included at build time. The reference server and\nclient provide access to the features of all of the built-in modules, and come\nwith exposure to all of their potential bugs and bloat.\n\nAs a result, we expect most users of SansShell would want to copy a very\nminimal set of the code (a handful of lines from the reference client and\nserver), import only the modules they intend to use, and build their own\nderivative of SansShell with more (or less!) functionality.\n\nThat same extensibility makes it easy to add additional functionality by\nimplementing your own module.\n\nTo quickly rebuild all binaries you can run:\n\n```\n$ go generate build.go\n```\n\nand they will be placed in a bin directory (which is ignored by git).\n\nTODO: Add example client and server, building in different SansShell modules.\n\nIf you need to edit a proto file (to augment an existing service or\ncreate a new one) you'll need to generate proto outputs.\n\n```\n$ go generate tools.go\n```\n\nNOTE: tools.go will need to have additions to it if you add new services.\n\n[1]: https://github.com/protocolbuffers/protobuf/releases\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FSnowflake-Labs%2Fsansshell","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FSnowflake-Labs%2Fsansshell","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FSnowflake-Labs%2Fsansshell/lists"}