Skip to content

Commit

Permalink
Add a hook for populating the principal in rpcauth input (#351)
Browse files Browse the repository at this point in the history
Many systems have the concept of an entity having an id and a set of groups, so the rpcauth input represents that in a first-class way. Until now, there was no method in this repo to populate the input. For multi-party-authentication, id and group will be an integral part so we'd benefit from having some way to represent them in tests for sansshell.

PeerPrincipalFromCertHook populates principal based on common name and organizational units in a cert. This is meant much more as an example of how principal can be populated and not as an endorsement of how it should be populated. I've regenerated certs to have both fields.

The upstream project we were using for generating certs has been deleted (a cached version lives on at https://pkg.go.dev/github.com/meterup/generate-cert) so I've switched to generating certs with openssl. This always feels a bit crufty to do but it has the benefit of using an extremely common tool that doesn't hide any of the details.

Part of #346

Co-authored-by: Edbert Linardi <[email protected]>
  • Loading branch information
stvnrhodes and sfc-gh-elinardi authored Oct 25, 2023
1 parent f8d2ce3 commit be9144e
Show file tree
Hide file tree
Showing 9 changed files with 196 additions and 69 deletions.
66 changes: 36 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ A non-interactive daemon for host management

```mermaid
flowchart LR;
subgraph sanssh ["sansshell client (sanssh)"]
cli;
client;
Expand Down Expand Up @@ -55,11 +55,12 @@ audited in advance or after the fact, and is ideally deterministic (for a given
state of the local machine).

sanssh is a simple CLI with a friendly API for dumping debugging state and
interacting with a remote machine. It also includes a set of convenient but
interacting with a remote machine. It also includes a set of convenient but
perhaps-less-friendly subcommands to address the raw SansShell API endpoints.

# Getting Started
How to set up, build and run locally for testing. All commands are relative to

How to set up, build and run locally for testing. All commands are relative to
the project root directory.

Building SansShell requires a recent version of Go (check the go.mod file for
Expand All @@ -74,6 +75,7 @@ $ cp -r auth/mtls/testdata ~/.sansshell
```

Then you can build and run the server, in separate terminal windows:

```
$ go run ./cmd/sansshell-server
$ go run ./cmd/sanssh --targets=localhost file read /etc/hosts
Expand All @@ -96,6 +98,7 @@ buffer compiler (`protoc`) (version 3 or above) as well as the protoc plugins
for Go and Go-GRPC

On MacOS, the protocol buffer can be installed via homebrew using

```
brew install protobuf
```
Expand All @@ -105,7 +108,7 @@ directly installing a release version from the [protocol buffers github][1]

## Environment setup : protoc plugins.

On any platform, Once protoc has been installed, you can install the required
On any platform, once protoc has been installed, you can install the required
code generation plugins using `go install`.

```
Expand All @@ -126,17 +129,11 @@ $ go generate tools.go

## Creating your own certificates

As an alternative to copying auth/mtls/testdata, you can create your onwn example mTLS certs:
```
$ go install github.com/meterup/generate-cert@latest
$ mkdir -m 0700 certs
$ cd certs
$ $(go env GOPATH)/bin/generate-cert --host=localhost,127.0.0.1,::1
$ cd ../
$ ln -s $(pwd)/certs ~/.sansshell
```
As an alternative to copying auth/mtls/testdata, you can create your own example mTLS certs. See the
[mtls testdata readme](/auth/mtls/testdata/README.md) for steps.

## Debugging

Reflection is included in the RPC servers (proxy and sansshell-server)
allowing for the use of [grpc_cli](https://github.com/grpc/grpc/blob/master/doc/command_line_tool.md).

Expand All @@ -152,25 +149,29 @@ $ GRPC_DEFAULT_SSL_ROOTS_FILE_PATH=$HOME/.sansshell/root.pem grpc_cli \
NOTE: This connects to the proxy. Change to 50042 if you want to connect to the sansshell-server.

# A tour of the codebase

SansShell is composed of 5 primary concepts:
1. A series of services, which live in the `services/` directory.
1. A server which wraps these services into a local host agent.
1. A proxy server which can be used as an entry point to processing sansshell
RPCs by validating policy and then doing fanout to 1..N actual
sansshell servers. This can be done as a one to many RPC where
a single incoming RPC is replicated to N backend hosts in one RPC call.
1. A reference server binary, which includes all of the services.
1. A CLI, which serves as the reference implementation of how to use the
services via the agent.

1. A series of services, which live in the `services/` directory.
1. A server which wraps these services into a local host agent.
1. A proxy server which can be used as an entry point to processing sansshell
RPCs by validating policy and then doing fanout to 1..N actual
sansshell servers. This can be done as a one to many RPC where
a single incoming RPC is replicated to N backend hosts in one RPC call.
1. A reference server binary, which includes all of the services.
1. A CLI, which serves as the reference implementation of how to use the
services via the agent.

## Services

Services implement at least one gRPC API endpoint, and expose it by calling
`RegisterSansShellService` from `init()`. The goal is to allow custom
`RegisterSansShellService` from `init()`. The goal is to allow custom
implementations of the SansShell Server to easily import services they wish to
use, and have zero overhead or risk from services they do not import at compile
time.

### List of available Services:

1. Ansible: Run a local ansible playbook and return output
1. Execute: Execute a command
1. HealthCheck
Expand All @@ -180,29 +181,32 @@ time.
1. Process operations: List, Get stacks (native or Java), Get dumps (core or Java heap)
1. Service operations: List, Status, Start/stop/restart


TODO: Document service/.../client expectations.

## The Server class

Most of the logic of instantiating a local SansShell server lives in the
`server` directory. This instantiates a gRPC server, registers the imported
`server` directory. This instantiates a gRPC server, registers the imported
services with that server, and constraints them with the supplied OPA policy.

## The reference Proxy Server binary

There is a reference implementation of a SansShell Proxy Server in
`cmd/proxy-server`, which should be suitable as-written for many use cases.
It's intentionally kept relatively short, so that it can be copied to another
repository and customized by adjusting only the imported services.

## The reference Server binary

There is a reference implementation of a SansShell Server in
`cmd/sansshell-server`, which should be suitable as-written for many use cases.
It's intentionally kept relatively short, so that it can be copied to another
repository and customized by adjusting only the imported services.

## The reference CLI client

There is a reference implementation of a SansShell CLI Client in
`cmd/sanssh`. It provides raw access to each gRPC endpoint, as well
`cmd/sanssh`. It provides raw access to each gRPC endpoint, as well
as a way to implement "convenience" commands which chain together a series of
actions.

Expand All @@ -219,11 +223,12 @@ complete -C /path/to/sanssh -o dirnames sanssh
```

# Extending SansShell
SansShell is built on a principle of "Don't pay for what you don't use". This

SansShell is built on a principle of "Don't pay for what you don't use". This
is advantageous in both minimizing the resources of SansShell server (binary
size, memory footprint, etc) as well as reducing the security risk of running
it. To accomplish that, all of the SansShell services are independent modules,
which can be optionally included at build time. The reference server and
it. To accomplish that, all of the SansShell services are independent modules,
which can be optionally included at build time. The reference server and
client provide access to the features of all of the built-in modules, and come
with exposure to all of their potential bugs and bloat.

Expand All @@ -236,6 +241,7 @@ That same extensibility makes it easy to add additional functionality by
implementing your own module.

To quickly rebuild all binaries you can run:

```
$ go generate build.go
```
Expand All @@ -244,7 +250,7 @@ and they will be placed in a bin directory (which is ignored by git).

TODO: Add example client and server, building in different SansShell modules.

If you need to edit a proto file (to augment an existing service or
If you need to edit a proto file (to augment an existing service or
create a new one) you'll need to generate proto outputs.

```
Expand Down
57 changes: 49 additions & 8 deletions auth/mtls/mtls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,26 +62,52 @@ allow {
input.peer.net.network = "something else"
}
`
allowPeerSerialPolicy = `
allowPeerCommonName = `
package sansshell.authz
default allow = false
allow {
input.type = "google.protobuf.Empty"
input.method = "/HealthCheck.HealthCheck/Ok"
input.peer.cert.subject.SerialNumber = "18469474828185269013890292403630281187"
input.peer.cert.subject.CommonName = "sanssh"
}
`
denyPeerSerialPolicy = `
denyPeerCommonName = `
package sansshell.authz
default allow = false
allow {
input.type = "google.protobuf.Empty"
input.method = "/HealthCheck.HealthCheck/Ok"
input.peer.cert.subject.SerialNumber = "12345"
input.peer.cert.subject.CommonName = "not-sanssh"
}
`

allowPeerPrincipal = `
package sansshell.authz
default allow = false
allow {
input.type = "google.protobuf.Empty"
input.method = "/HealthCheck.HealthCheck/Ok"
input.peer.principal.id = "sanssh"
input.peer.principal.groups[_] = "group2"
}
`

denyPeerPrincipal = `
package sansshell.authz
default allow = false
allow {
input.type = "google.protobuf.Empty"
input.method = "/HealthCheck.HealthCheck/Ok"
input.peer.principal.id = "sanssh"
input.peer.principal.groups[_]= "group3"
}
`
)
Expand Down Expand Up @@ -144,6 +170,7 @@ func serverWithPolicy(t *testing.T, policy string) (*bufconn.Listener, *grpc.Ser
server.WithCredentials(creds),
server.WithPolicy(policy),
server.WithAuthzHook(rpcauth.HostNetHook(lis.Addr())),
server.WithAuthzHook(rpcauth.PeerPrincipalFromCertHook()),
)
testutil.FatalOnErr("Could not build server", err, t)
listening := make(chan struct{})
Expand Down Expand Up @@ -302,13 +329,23 @@ func TestHealthCheck(t *testing.T) {
err: "OPA policy does not permit this request",
},
{
name: "allowed peer by subject serial",
policy: allowPeerSerialPolicy,
name: "allowed peer by subject common name",
policy: allowPeerCommonName,
err: "",
},
{
name: "denied peer by subject common name",
policy: denyPeerCommonName,
err: "OPA policy does not permit this request",
},
{
name: "allowed peer by principal parsed from cert",
policy: allowPeerPrincipal,
err: "",
},
{
name: "denied peer by subject serial",
policy: denyPeerSerialPolicy,
name: "denied peer by principal parsed from cert",
policy: denyPeerPrincipal,
err: "OPA policy does not permit this request",
},
} {
Expand All @@ -331,6 +368,10 @@ func TestHealthCheck(t *testing.T) {
}
return
}
if err == nil && tc.err != "" {
t.Errorf("Passed, but expected error: %v", tc.err)

}
t.Logf("Response: %+v", resp)
s.GracefulStop()
})
Expand Down
63 changes: 63 additions & 0 deletions auth/mtls/testdata/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# mtls testdata

This directory contains some test certificates.

If you need to regenerate them, here's some helpful commands. Openssl

## Generating private keys

These commands will generate new private keys.

```bash
openssl genrsa -out client.key
openssl genrsa -out leaf.key
openssl genrsa -out root.key
```

## Generating certificates

These commands will generate new certificates. We need to generate certificate signing requests as part of generating certificates, but we can delete them afterwards.

```bash
# CA cert
openssl req -new -key root.key -out root.csr -subj "/O=Acme Co"
openssl x509 -req -days 30000 -in root.csr -signkey root.key -out root.pem

# Cert for the client
openssl req -new -key client.key -out client.csr -subj "/O=Acme Co/OU=group1/OU=group2/CN=sanssh"
openssl x509 -req -days 3000 -in client.csr -CA root.pem -CAkey root.key -out client.pem -extensions req_ext -extfile /dev/stdin <<EOF
[req_ext]
keyUsage = critical, digitalSignature
extendedKeyUsage = clientAuth
basicConstraints = critical, CA:FALSE
EOF

# Cert for the server and/or proxy
openssl req -new -key leaf.key -out leaf.csr -subj "/O=Acme Co/OU=group2/OU=group3/CN=sansshell-server"
openssl x509 -req -days 3000 -in leaf.csr -CA root.pem -CAkey root.key -out leaf.pem -extensions req_ext -extfile /dev/stdin <<EOF
[req_ext]
keyUsage = critical, digitalSignature
extendedKeyUsage = serverAuth
basicConstraints = critical, CA:FALSE
subjectAltName = @alt_names
[alt_names]
DNS.0 = localhost
DNS.1 = bufnet
IP.0 = 127.0.0.1
IP.1 = ::1
EOF

# Cleanup
rm root.csr client.csr leaf.csr
```

## Viewing contents

These commands will print out the information embedded in the certificates.

```bash
openssl x509 -in client.pem -text -noout
openssl x509 -in leaf.pem -text -noout
openssl x509 -in root.pem -text -noout
```
22 changes: 11 additions & 11 deletions auth/mtls/testdata/client.pem
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
-----BEGIN CERTIFICATE-----
MIIB8jCCAZigAwIBAgIQDeUXahxXBDJOji0oB9CR4zAKBggqhkjOPQQDAjBEMRAw
DgYDVQQKEwdBY21lIENvMTAwLgYDVQQFEycxODMwMjA0MTkxMDE0MDc5MzAxNTAz
MzM0MzkxMzAxMjY4Njk1NjcwIBcNMjIwODI1MTkzNTQ4WhgPMjEyMjA4MDExOTM1
NDhaMEMxEDAOBgNVBAoTB0FjbWUgQ28xLzAtBgNVBAUTJjE4NDY5NDc0ODI4MTg1
MjY5MDEzODkwMjkyNDAzNjMwMjgxMTg3MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcD
QgAE3eTKb2g1hn/F3DWvihN9hhEAIRqWxE6K/06zPCZpoVv4ueyz0Cs5Zs0GiP2L
a7elGE96EMiiidskXASV8XvSQKNrMGkwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQM
MAoGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwNAYDVR0RBC0wK4IJbG9jYWxob3N0
ggZidWZuZXSHBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwCgYIKoZIzj0EAwIDSAAw
RQIhAPRJYvA6qEaw51SzYMru4Afgr0kRSrbXzCwJvJYUeoYfAiAF+2ke82tpOimS
4V05SsI6xQKwz3R+XsBxV+Xb0mlDdg==
MIIB6DCCAY6gAwIBAgIUBHQ+cVYuTDKVbmtUYhFLhvoARkUwCgYIKoZIzj0EAwIw
EjEQMA4GA1UECgwHQWNtZSBDbzAeFw0yMzEwMTIwNjQ0MTFaFw0zMTEyMjkwNjQ0
MTFaMEUxEDAOBgNVBAoMB0FjbWUgQ28xDzANBgNVBAsMBmdyb3VwMTEPMA0GA1UE
CwwGZ3JvdXAyMQ8wDQYDVQQDDAZzYW5zc2gwWTATBgcqhkjOPQIBBggqhkjOPQMB
BwNCAATd5MpvaDWGf8XcNa+KE32GEQAhGpbETor/TrM8JmmhW/i57LPQKzlmzQaI
/Ytrt6UYT3oQyKKJ2yRcBJXxe9JAo4GOMIGLMA4GA1UdDwEB/wQEAwIHgDATBgNV
HSUEDDAKBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBQUALNsaT3z
hJowSYyL6oJuLiV4oTA3BgNVHSMEMDAuoRakFDASMRAwDgYDVQQKDAdBY21lIENv
ghQZhnpcR3ezVjHJSdcE1sQJ85IaXTAKBggqhkjOPQQDAgNIADBFAiEA/Vld9pPP
i97LoCs78GCccyRpS5obPU6us+CmlxBz5YQCIAcKM4GHk0iT8LJUDAAhxlI78DHT
B26YBll73+XzPIQ8
-----END CERTIFICATE-----
23 changes: 12 additions & 11 deletions auth/mtls/testdata/leaf.pem
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
-----BEGIN CERTIFICATE-----
MIIB8zCCAZqgAwIBAgIRANyshotLwGS10UNSQnyNeQ0wCgYIKoZIzj0EAwIwRDEQ
MA4GA1UEChMHQWNtZSBDbzEwMC4GA1UEBRMnMTgzMDIwNDE5MTAxNDA3OTMwMTUw
MzMzNDM5MTMwMTI2ODY5NTY3MCAXDTIyMDgyNTE5MzU0OFoYDzIxMjIwODAxMTkz
NTQ4WjBEMRAwDgYDVQQKEwdBY21lIENvMTAwLgYDVQQFEycyOTMzMjU5NjMwMTEz
OTQ5MjYyODIwNzkzNzI3NDkzMTI5ODEyNjEwWTATBgcqhkjOPQIBBggqhkjOPQMB
BwNCAARG7kHsuuB/ctK1/TWHmgmHA/yUasH9IGr13kjq8t1AuIFykHj/JO9CKs2V
KpGDYlAjQrUJw0Mz97CnBeT6phLAo2swaTAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0l
BAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADA0BgNVHREELTArgglsb2NhbGhv
c3SCBmJ1Zm5ldIcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATAKBggqhkjOPQQDAgNH
ADBEAiAdD/JrMl4cXHVpOBG8XxkFl0pB4ZF0pjB+uDJef/NgDAIgGIbWI8FryVcH
wCjTGsatA4v3P+wPFIQcF6KfBP8s4MQ=
MIICKTCCAc6gAwIBAgIUWx3aT2/T11gDmxyCd3L+QD0Wg78wCgYIKoZIzj0EAwIw
EjEQMA4GA1UECgwHQWNtZSBDbzAeFw0yMzEwMTIwNjQ0MTFaFw0zMTEyMjkwNjQ0
MTFaME8xEDAOBgNVBAoMB0FjbWUgQ28xDzANBgNVBAsMBmdyb3VwMjEPMA0GA1UE
CwwGZ3JvdXAzMRkwFwYDVQQDDBBzYW5zc2hlbGwtc2VydmVyMFkwEwYHKoZIzj0C
AQYIKoZIzj0DAQcDQgAERu5B7Lrgf3LStf01h5oJhwP8lGrB/SBq9d5I6vLdQLiB
cpB4/yTvQirNlSqRg2JQI0K1CcNDM/ewpwXk+qYSwKOBxDCBwTAOBgNVHQ8BAf8E
BAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADA0BgNVHREE
LTArgglsb2NhbGhvc3SCBmJ1Zm5ldIcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATAd
BgNVHQ4EFgQUxoF5QgVDWLKmUDHAl42ksrMK8mowNwYDVR0jBDAwLqEWpBQwEjEQ
MA4GA1UECgwHQWNtZSBDb4IUGYZ6XEd3s1YxyUnXBNbECfOSGl0wCgYIKoZIzj0E
AwIDSQAwRgIhAIGxEm8/f2f7lQSVI5TzkISy3jSiOAzDhoqFj4BnMVODAiEAg8MM
yrNmx+4Tloz7eDaYsPriIWWuRbFntRMjwte0uL4=
-----END CERTIFICATE-----
Loading

0 comments on commit be9144e

Please sign in to comment.