Skip to content

Commit f984759

Browse files
committed
Modify cert usage; allow users to manage certs.
Steno's use of certificates for user/group verification initially was designed a little wonky: 1) the server cert was used to sign the client cert 2) certs (both client and server) were recreated from scratch on each run of steno These two issues made it really hard to do anything more complex with certs, like having a single client cert that works across multiple servers, or using an organization's central CA for cert management. This CL changes that: 1) Certificate creation is now done at install time (stenokeys.sh) with the 'openssl' command-line tool, not inside Go code. Thus, keys are no longer rewritten on startup. 2) There is now a CA cert in /etc/stenographer/certs/ca_cert.pem. This CA cert is used to sign both the client and server cert. * Users can now overwrite it with their org's CA cert to centrally manage certificates. * Users can generate multiple client or server certs from a single CA cert and manually distribute them. This is somewhat backwards-incompatible with old installs of steno, which don't have a ca_cert. Thus, when install.sh is run now, if it sees that ca_cert is missing, it'll wipe all certs and reinstall all certs from scratch. Note, this should also fix Issue google#124, where NSS TLS is more stringent with certs and doesn't allow a CA cert to also act as a server cert.
1 parent 20fddc5 commit f984759

File tree

9 files changed

+229
-196
lines changed

9 files changed

+229
-196
lines changed

DESIGN.md

+30-23
Original file line numberDiff line numberDiff line change
@@ -77,29 +77,36 @@ user requests.
7777

7878
#### Access Control ####
7979

80-
Access to raw packet data is, of course, sensitive. We wanted to find a way to
81-
provide and revoke access easily, using simple POSIX permissions. Here's the
82-
system we came up with on very short notice:
83-
84-
* On startup, stenographer creates a server cert/key pair and client cert/key
85-
pair in a certificate directory. The server key is readable only by
86-
the analyst running stenographer. The server cert and client cert/key are
87-
readable by the POSIX `stenographer` group.
88-
* The server uses the server cert/key as its TLS cert/key when serving data.
89-
The client, when issuing TLS requests, verifies the server is valid by
90-
checking against the server cert it can read in the cerficates directory.
91-
* The server does client verification using the client cert, which it can
92-
read from the certificate directory. In order to create a valid TLS
93-
connection, then, the client must have access to the client key, which is
94-
only visible to the stenographer POSIX group.
95-
96-
Granting access to stenographer on a local machine is as simple as adding a
97-
analyst to the `stenographer` POSIX group, thus allowing them read access to the
98-
client key in the certificate directory. Revoking access involves removing the
99-
analyst from the POSIX group, then restarting stenographer in order to generate
100-
new certs/keys (and thus invalidate any old keys the analyst may have copied to
101-
other locations).
102-
80+
Access to the server is controlled with client certificates. On install, a
81+
script, `stenokeys.sh`, is run to generate a CA certificate and use it to
82+
create/sign a client and server certificate. The client and server authenticate
83+
each other on every request using the CA certificate as a source of truth.
84+
POSIX permissions are used locally to control access to the certs... the
85+
`stenographer` user which runs steno has read access to the server key
86+
(`steno:root -r--------`). The `stenographer` group as read access to the
87+
client key (`root:steno ----r-----`). Key usage extensions specify that the
88+
server key must be used as a TLS server, and the client key must be used as a
89+
TLS client.
90+
91+
Due to the file permissions mentioned above, giving steno access to a local user
92+
simply requires adding that user to the local `stenographer` group, thus giving
93+
them access to `client_key.pem`.
94+
95+
Once keys are created on install, they're currently NEVER REVOKED. Thus, if
96+
someone gets access to a client cert, they'll have access to the server ad
97+
infinitum. Should you have problems with a key being released, the current best
98+
way to handle this is by deleting all data in the `/etc/stenographer/certs`
99+
directory and rerunning `stenokeys.sh` to generate an entirely new set of keys
100+
rooted to a new CA.
101+
102+
`stenokeys.sh` will not modify keys/certs that already exist in
103+
`/etc/stenographer/certs`. Thus, if you have more complex topologies, you can
104+
overwrite these values and they'll happily be used by Stenographer. If, for
105+
example, you already have a CA in your organization, you can copy its cert into
106+
the `ca_cert.pem` file, then create `{client,server}_{key,cert}.pem` files
107+
rooted in that CA and copy them in. This also allows folks to use a single CA
108+
cert over multiple stenographer instances, allowing a single client cert to
109+
access multiple servers over the network.
103110

104111
### Stenotype ###
105112

certs/certs.go

-83
Original file line numberDiff line numberDiff line change
@@ -18,96 +18,13 @@
1818
package certs
1919

2020
import (
21-
"crypto/rand"
22-
"crypto/rsa"
2321
"crypto/tls"
2422
"crypto/x509"
25-
"crypto/x509/pkix"
2623
"encoding/pem"
2724
"fmt"
2825
"io/ioutil"
29-
"math/big"
30-
"net"
31-
"os"
32-
"time"
3326
)
3427

35-
const (
36-
bits = 2048
37-
validFor = 365 * 24 * time.Hour
38-
)
39-
40-
// WriteNewCerts generates a self-signed certificate pair for use in
41-
// locally authorizing clients. If 'server' is true, it writes out certs
42-
// which can be used to verify the server, otherwise it writes out certs
43-
// clients can use to authorize themselves to the server.
44-
func WriteNewCerts(certFile, keyFile string, server bool) error {
45-
// Implementation mostly taken from http://golang.org/src/pkg/crypto/tls/generate_cert.go
46-
priv, err := rsa.GenerateKey(rand.Reader, bits)
47-
if err != nil {
48-
return fmt.Errorf("failed to generate private key: %v", err)
49-
}
50-
51-
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
52-
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
53-
if err != nil {
54-
return fmt.Errorf("failed to generate serial number: %v", err)
55-
}
56-
57-
template := x509.Certificate{
58-
SerialNumber: serialNumber,
59-
Subject: pkix.Name{
60-
Organization: []string{"Stenographer"},
61-
CommonName: "127.0.0.1",
62-
},
63-
NotBefore: time.Now(),
64-
NotAfter: time.Now().Add(validFor),
65-
66-
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
67-
BasicConstraintsValid: true,
68-
DNSNames: []string{"localhost"},
69-
IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1)},
70-
IsCA: true, // we're self-signed.
71-
}
72-
var keyFileMode os.FileMode
73-
if server {
74-
template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
75-
keyFileMode = 0600
76-
} else {
77-
template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}
78-
keyFileMode = 0640
79-
}
80-
81-
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
82-
if err != nil {
83-
return fmt.Errorf("Failed to create certificate: %v", err)
84-
}
85-
86-
// Actually start writing.
87-
certOut, err := os.Create(certFile)
88-
if err != nil {
89-
return fmt.Errorf("failed to open %q for writing: %s", certFile, err)
90-
}
91-
if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
92-
return fmt.Errorf("could not encode pem: %v", err)
93-
}
94-
if err := certOut.Close(); err != nil {
95-
return fmt.Errorf("could not close cert file: %v", err)
96-
}
97-
98-
keyOut, err := os.OpenFile(keyFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, keyFileMode)
99-
if err != nil {
100-
return fmt.Errorf("failed to open %q for writing: %v", keyFile, err)
101-
}
102-
if err := pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}); err != nil {
103-
return fmt.Errorf("could not encode key: %v", err)
104-
}
105-
if err := keyOut.Close(); err != nil {
106-
return fmt.Errorf("could not close key file: %v", err)
107-
}
108-
return nil
109-
}
110-
11128
// ClientVerifyingTLSConfig returns a TLS config which verifies that clients
11229
// have a certificate signed by the CA certificate in the certFile.
11330
func ClientVerifyingTLSConfig(certFile string) (*tls.Config, error) {

certs/certs_test.go

-52
This file was deleted.

env/env.go

+7-16
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ var (
4949
const (
5050
fileSyncFrequency = 15 * time.Second
5151

52-
// These files will be generated in Config.CertPath.
53-
clientCertFilename = "client_cert.pem"
54-
clientKeyFilename = "client_key.pem"
52+
// These files will be read from Config.CertPath.
53+
// Use stenokeys.sh to generate them.
54+
caCertFilename = "ca_cert.pem"
5555
serverCertFilename = "server_cert.pem"
5656
serverKeyFilename = "server_key.pem"
5757
)
@@ -60,18 +60,7 @@ const (
6060
// requests. This server will server over TLS, using the certs
6161
// stored in c.CertPath to verify itself to clients and verify clients.
6262
func (e *Env) Serve() error {
63-
clientCert, clientKey, serverCert, serverKey :=
64-
filepath.Join(e.conf.CertPath, clientCertFilename),
65-
filepath.Join(e.conf.CertPath, clientKeyFilename),
66-
filepath.Join(e.conf.CertPath, serverCertFilename),
67-
filepath.Join(e.conf.CertPath, serverKeyFilename)
68-
if err := certs.WriteNewCerts(clientCert, clientKey, false); err != nil {
69-
return fmt.Errorf("cannot write client certs: %v", err)
70-
}
71-
if err := certs.WriteNewCerts(serverCert, serverKey, true); err != nil {
72-
return fmt.Errorf("cannot write server certs: %v", err)
73-
}
74-
tlsConfig, err := certs.ClientVerifyingTLSConfig(clientCert)
63+
tlsConfig, err := certs.ClientVerifyingTLSConfig(filepath.Join(e.conf.CertPath, caCertFilename))
7564
if err != nil {
7665
return fmt.Errorf("cannot verify client cert: %v", err)
7766
}
@@ -81,7 +70,9 @@ func (e *Env) Serve() error {
8170
}
8271
http.HandleFunc("/query", e.handleQuery)
8372
http.Handle("/debug/stats", stats.S)
84-
return server.ListenAndServeTLS(serverCert, serverKey)
73+
return server.ListenAndServeTLS(
74+
filepath.Join(e.conf.CertPath, serverCertFilename),
75+
filepath.Join(e.conf.CertPath, serverKeyFilename))
8576
}
8677

8778
func (e *Env) handleQuery(w http.ResponseWriter, r *http.Request) {

install.sh

+21-15
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,30 @@ source lib.sh
2727
set -e
2828
Info "Making sure we have sudo access"
2929
sudo cat /dev/null
30-
set +e
31-
32-
Info "Killing aleady-running processes"
33-
sudo service stenographer stop
34-
ReallyKill stenographer
35-
ReallyKill stenotype
3630

37-
set -e
3831
InstallPackage libaio-dev
3932
InstallPackage libleveldb-dev
4033
InstallPackage libsnappy-dev
4134
InstallPackage g++
4235
InstallPackage libcap2-bin
4336
InstallPackage libseccomp-dev
37+
InstallPackage jq
38+
InstallPackage openssl
39+
40+
Info "Building stenographer"
41+
go build
42+
43+
Info "Building stenotype"
44+
pushd stenotype
45+
make
46+
popd
47+
48+
set +e
49+
Info "Killing aleady-running processes"
50+
sudo service stenographer stop
51+
ReallyKill stenographer
52+
ReallyKill stenotype
53+
set -e
4454

4555
if ! id stenographer >/dev/null 2>&1; then
4656
Info "Setting up stenographer user"
@@ -65,7 +75,7 @@ fi
6575
if [ ! -d /etc/stenographer/certs ]; then
6676
Info "Setting up stenographer /etc directory"
6777
sudo mkdir -p /etc/stenographer/certs
68-
sudo chown -R stenographer:stenographer /etc/stenographer/certs
78+
sudo chown -R root:root /etc/stenographer/certs
6979
if [ ! -f /etc/stenographer/config ]; then
7080
sudo cp -vf configs/steno.conf /etc/stenographer/config
7181
sudo chown root:root /etc/stenographer/config
@@ -80,16 +90,12 @@ if grep -q /path/to /etc/stenographer/config; then
8090
exit 1
8191
fi
8292

83-
Info "Building stenographer"
84-
go build
93+
sudo ./stenokeys.sh /etc/stenographer/certs stenographer stenographer
94+
95+
Info "Copying stenographer/stenotype"
8596
sudo cp -vf stenographer "$BINDIR/stenographer"
8697
sudo chown stenographer:root "$BINDIR/stenographer"
8798
sudo chmod 0700 "$BINDIR/stenographer"
88-
89-
Info "Building stenotype"
90-
pushd stenotype
91-
make
92-
popd
9399
sudo cp -vf stenotype/stenotype "$BINDIR/stenotype"
94100
sudo chown stenographer:root "$BINDIR/stenotype"
95101
sudo chmod 0500 "$BINDIR/stenotype"

install_el7.sh

+12-5
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
export KILLCMD=/usr/bin/pkill
2626
export BINDIR="${BINDIR-/usr/bin}"
2727
export GOPATH=${HOME}/go
28-
export PATH=${PATH}/usr/local/go/bin
28+
export PATH=${PATH}:/usr/local/go/bin
2929

3030

3131
# Load support functions
@@ -101,7 +101,7 @@ install_configs () {
101101
Info "Setting up stenographer conf directory"
102102
if [ ! -d /etc/stenographer/certs ]; then
103103
sudo mkdir -p /etc/stenographer/certs
104-
sudo chown -R stenographer:stenographer /etc/stenographer/certs
104+
sudo chown -R root:root /etc/stenographer/certs
105105
fi
106106
if [ ! -f /etc/stenographer/config ]; then
107107
sudo cp -vf configs/steno.conf /etc/stenographer/config
@@ -117,6 +117,12 @@ install_configs () {
117117
fi
118118
}
119119

120+
install_certs () {
121+
cd $_scriptDir
122+
123+
sudo ./stenokeys.sh /etc/stenographer/certs stenographer stenographer
124+
}
125+
120126
install_service () {
121127
cd $_scriptDir
122128

@@ -195,14 +201,15 @@ start_service () {
195201
}
196202

197203
check_sudo
198-
stop_processes
199204
install_packages
200205
install_golang
206+
build_stenographer
207+
build_stenotype
201208
install_jq
202209
add_accounts
203210
install_configs
211+
install_certs
204212
install_service
205-
build_stenographer
206-
build_stenotype
207213
install_stenoread
214+
stop_processes
208215
start_service

integration_test/test.sh

+7
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,15 @@ popd
6666

6767
Info "Setting up output directory"
6868
OUTDIR="$(mktemp -d $BASEDIR/steno.XXXXXXXXXX)"
69+
/bin/chmod g+rx "$OUTDIR"
6970
Info "Writing output to directory '$OUTDIR'"
7071

7172
mkdir $OUTDIR/{pkt,idx,certs}
73+
Info "Setting up certs"
74+
CURR_USR="$(id -u -n)"
75+
CURR_GRP="$(id -g -n)"
76+
../stenokeys.sh $OUTDIR/certs $CURR_USR $CURR_GRP
77+
7278
Info "Setting up $DUMMY interface"
7379
sudo /sbin/modprobe dummy
7480
sudo ip link add $DUMMY type dummy || Error "$DUMMY may already exist"
@@ -90,6 +96,7 @@ function CleanUp {
9096
fi
9197
Info "Deleting $DUMMY interface"
9298
Info "Removing $OUTDIR"
99+
sudo find $OUTDIR -ls
93100
rm -rfv $OUTDIR
94101
sudo ifconfig $DUMMY down
95102
sudo ip link del dummy0

0 commit comments

Comments
 (0)