This repository has been archived by the owner. It is now read-only.
Permalink
Browse files

tls: Don't allow unencrypted GRPC connections

Before, if no `tls-dir` was supplied, Quilt would still connect to the
cluster using an unencrypted channel. This insecure-by-default behavior
made it too easy for users to run insecurely. Furthermore, the secret
management design will make use of the TLS credentials, so if we allowed
insecure deployments, secrets would not work for those deployments. This
commit makes it so that neither connections to the cluster, nor
connections to the daemon happen without TLS encryption.

To decrease the chances of accidentally booting the cluster with one set
of credentials, but trying to connect with another set, this commit also
removes the `tls-dir` flag. Instead, TLS credentials are always read
from `~/.quilt/tls`.
  • Loading branch information...
kklin committed Sep 27, 2017
1 parent decca34 commit 239f742b8132316acd936fcd4c7b82467126ea6c
Showing with 188 additions and 331 deletions.
  1. +3 −2 CHANGELOG.md
  2. +1 −12 api/server/server.go
  3. +4 −6 cli/command/connection.go
  4. +0 −25 cli/command/credentials/credentials.go
  5. +0 −38 cli/command/credentials/credentials_test.go
  6. +23 −32 cli/command/daemon.go
  7. +1 −1 cli/command/minion.go
  8. +17 −0 cli/path/path.go
  9. +1 −1 connection/credentials/credentials_test.go → cli/path/path_test.go
  10. +7 −17 cloud/cfg/cfg.go
  11. +5 −4 cloud/cfg/cfg_test.go
  12. +1 −3 cloud/cloud.go
  13. +9 −9 cloud/credentials.go
  14. +11 −8 cloud/credentials_test.go
  15. +0 −18 connection/credentials/insecure.go
  16. +6 −2 connection/{credentials → }/tls/io/io.go
  17. +1 −1 connection/{credentials → }/tls/io/io_test.go
  18. 0 connection/{credentials → }/tls/rsa/rsa.go
  19. +1 −1 connection/{credentials → }/tls/rsa/rsa_test.go
  20. 0 connection/{credentials → }/tls/tls.go
  21. +1 −1 connection/{credentials → }/tls/tls_test.go
  22. +13 −23 docs/_Security.md
  23. +3 −7 integration-tester/exec.go
  24. +5 −10 integration-tester/tester.go
  25. +3 −5 integration-tester/tests/10-network/network_test.go
  26. +2 −4 integration-tester/tests/100-logs/logs_test.go
  27. +2 −4 integration-tester/tests/15-bandwidth/bandwidth_test.go
  28. +2 −4 integration-tester/tests/20-spark/spark_test.go
  29. +1 −4 integration-tester/tests/30-mean/mean_test.go
  30. +2 −4 integration-tester/tests/40-stop/stop_test.go
  31. +2 −4 integration-tester/tests/61-duplicate-cluster/duplicate_cluster_test.go
  32. +2 −4 integration-tester/tests/build-dockerfile/custom_images_test.go
  33. +22 −30 integration-tester/tests/connection-credentials/credentials_test.go
  34. +1 −4 integration-tester/tests/django/django_test.go
  35. +2 −4 integration-tester/tests/elasticsearch/elasticsearch_test.go
  36. +2 −4 integration-tester/tests/etcd/etcd_test.go
  37. +2 −4 integration-tester/tests/floating-ip/floating_ip_test.go
  38. +2 −4 integration-tester/tests/haproxy/haproxy_test.go
  39. +1 −4 integration-tester/tests/inbound-public/inbound_public_test.go
  40. +2 −4 integration-tester/tests/load-balancer/load_balancer_test.go
  41. +1 −4 integration-tester/tests/lobsters/lobsters_test.go
  42. +2 −4 integration-tester/tests/outbound-public/outbound_public_test.go
  43. +2 −4 integration-tester/tests/redis/redis_test.go
  44. +2 −4 integration-tester/tests/zookeeper/zookeeper_test.go
  45. +15 −0 integration-tester/util/util.go
  46. +3 −3 minion/run.go
View
@@ -5,8 +5,9 @@ Up Next
-------------
- Fix a bug where `quilt setup-tls` would fail when writing to a directory whose
parent does not exist.
- Auto-generate TLS credentials when starting the daemon if the provided
credentials don't exist.
- Auto-generate TLS credentials when starting the daemon if the credentials in
`~/.quilt/tls` don't already exist.
- Always encrypt communication with the daemon.
JavaScript API-breaking changes:
- Remove the Container.replicate() method. Users should create multiple
View
@@ -14,7 +14,6 @@ import (
"github.com/quilt/quilt/api/pb"
"github.com/quilt/quilt/blueprint"
"github.com/quilt/quilt/connection"
"github.com/quilt/quilt/connection/credentials"
"github.com/quilt/quilt/counter"
"github.com/quilt/quilt/db"
"github.com/quilt/quilt/version"
@@ -52,17 +51,7 @@ func Run(conn db.Conn, listenAddr string, runningOnDaemon bool,
return err
}
// Don't enforce TLS on inbound local connections. This way, users don't
// need to supply credentials when making local connections to the daemon
// (e.g. when running a spec). Instead, local connections should be secured
// using Unix permissions. Connections to the cluster (i.e. proxied
// connections) still need to use the proper credentials.
serverCreds := creds
if proto == "unix" {
serverCreds = credentials.Insecure{}
}
sock, s := connection.Server(proto, addr, serverCreds.ServerOpts())
sock, s := connection.Server(proto, addr, creds.ServerOpts())
// Cleanup the socket if we're interrupted.
sigc := make(chan os.Signal, 1)
View
@@ -5,19 +5,17 @@ import (
"github.com/quilt/quilt/api"
"github.com/quilt/quilt/api/client"
"github.com/quilt/quilt/cli/command/credentials"
cliPath "github.com/quilt/quilt/cli/path"
"github.com/quilt/quilt/connection"
tlsIO "github.com/quilt/quilt/connection/tls/io"
)
type connectionFlags struct {
host string
tlsDir string
host string
}
func (cf *connectionFlags) InstallFlags(flags *flag.FlagSet) {
flags.StringVar(&cf.host, "H", api.DefaultSocket, "the host to connect to")
flags.StringVar(&cf.tlsDir, "tls-dir", "",
"the directory in which to lookup tls certs")
}
type connectionHelper struct {
@@ -29,7 +27,7 @@ type connectionHelper struct {
func (ch *connectionHelper) BeforeRun() (err error) {
// Load the credentials that will be used by Quilt clients and servers.
ch.creds, err = credentials.Read(ch.tlsDir)
ch.creds, err = tlsIO.ReadCredentials(cliPath.DefaultTLSDir)
if err != nil {
return err
}

This file was deleted.

Oops, something went wrong.

This file was deleted.

Oops, something went wrong.
View
@@ -11,11 +11,10 @@ import (
"golang.org/x/crypto/ssh"
"github.com/quilt/quilt/api/server"
"github.com/quilt/quilt/cli/command/credentials"
cliPath "github.com/quilt/quilt/cli/path"
"github.com/quilt/quilt/cloud"
"github.com/quilt/quilt/connection/credentials/tls"
tlsIO "github.com/quilt/quilt/connection/credentials/tls/io"
"github.com/quilt/quilt/connection/credentials/tls/rsa"
tlsIO "github.com/quilt/quilt/connection/tls/io"
"github.com/quilt/quilt/connection/tls/rsa"
"github.com/quilt/quilt/db"
"github.com/quilt/quilt/engine"
"github.com/quilt/quilt/util"
@@ -71,16 +70,15 @@ func (dCmd *Daemon) AfterRun() error {
func (dCmd *Daemon) Run() int {
log.WithField("version", version.Version).Info("Starting Quilt daemon")
// If the specified TLS credential path does not exist, autogenerate
// credentials for the given path.
if dCmd.tlsDir != "" {
if _, err := util.Stat(dCmd.tlsDir); os.IsNotExist(err) {
log.WithField("path", dCmd.tlsDir).Info("Auto-generating TLS credentials")
if err := setupTLS(dCmd.tlsDir); err != nil {
log.WithError(err).WithField("path", dCmd.tlsDir).Error(
"TLS credential generation failed")
return 1
}
// If the TLS credentials do not exist, autogenerate credentials and write
// them to disk.
if _, err := util.Stat(cliPath.DefaultTLSDir); os.IsNotExist(err) {
log.Infof("TLS credentials not found in %s, so generating credentials "+
"and writing to disk", cliPath.DefaultTLSDir)
if err := setupTLS(cliPath.DefaultTLSDir); err != nil {
log.WithError(err).WithField("path", cliPath.DefaultTLSDir).Error(
"TLS credential generation failed")
return 1
}
}
@@ -93,11 +91,9 @@ func (dCmd *Daemon) Run() int {
"Failed to parse private key %s", dCmd.adminSSHPrivateKey)
return 1
}
}
if sshKey == nil && dCmd.tlsDir != "" {
log.Info("No admin key supplied, but TLS is enabled, which requires an " +
"admin SSH key to copy TLS credentials to the cluster. " +
} else {
log.Info("No admin key supplied, which is required " +
"to copy TLS credentials to the cluster. " +
"Auto-generating an in-memory key.")
var err error
sshKey, err = newSSHPrivateKey()
@@ -107,7 +103,7 @@ func (dCmd *Daemon) Run() int {
}
}
creds, err := credentials.Read(dCmd.tlsDir)
creds, err := tlsIO.ReadCredentials(cliPath.DefaultTLSDir)
if err != nil {
log.WithError(err).Error("Failed to parse TLS credentials")
return 1
@@ -117,20 +113,15 @@ func (dCmd *Daemon) Run() int {
go engine.Run(conn, getPublicKey(sshKey))
go server.Run(conn, dCmd.host, true, creds)
var minionTLSDir string
if _, isTLS := creds.(tls.TLS); isTLS {
minionTLSDir = "/home/quilt/.quilt/tls"
ca, err := tlsIO.ReadCA(dCmd.tlsDir)
if err != nil {
log.WithError(err).Error("Failed to parse certificate authority")
return 1
}
go cloud.SyncCredentials(conn, minionTLSDir, sshKey, ca)
ca, err := tlsIO.ReadCA(cliPath.DefaultTLSDir)
if err != nil {
log.WithError(err).WithField("path", cliPath.DefaultTLSDir).Error(
"Failed to parse certificate authority")
return 1
}
cloud.Run(conn, creds, minionTLSDir)
go cloud.SyncCredentials(conn, sshKey, ca)
cloud.Run(conn, creds)
return 0
}
View
@@ -84,6 +84,6 @@ func (mCmd *Minion) run() error {
return errors.New("no or improper role specified")
}
minion.Run(role, mCmd.inboundPubIntf, mCmd.outboundPubIntf, mCmd.tlsDir)
minion.Run(role, mCmd.inboundPubIntf, mCmd.outboundPubIntf)
return nil
}
View
@@ -0,0 +1,17 @@
package path
import (
"os"
"path/filepath"
)
var (
// quiltHome is where Quilt configuration (such as TLS credentials) are
// stored.
// The HOME environment variable is the user's home directory on all POSIX
// compatible systems.
quiltHome = filepath.Join(os.Getenv("HOME"), ".quilt")
// DefaultTLSDir is the default location for users to store TLS credentials.
DefaultTLSDir = filepath.Join(quiltHome, "tls")
)
@@ -1,4 +1,4 @@
package credentials
package path
import (
"testing"
View
@@ -6,6 +6,7 @@ import (
"strings"
"text/template"
tlsIO "github.com/quilt/quilt/connection/tls/io"
"github.com/quilt/quilt/db"
"github.com/quilt/quilt/version"
@@ -19,23 +20,17 @@ const (
// Allow mocking out for the unit tests.
var ver = version.Version
// MinionTLSDir is where minions should look for their TLS configuration on boot.
var MinionTLSDir string
// Ubuntu generates a cloud config file for the Ubuntu operating system with the
// corresponding `version`.
func Ubuntu(m db.Machine, inboundPublic string) string {
t := template.Must(template.New("cloudConfig").Parse(cfgTemplate))
img := fmt.Sprintf("%s:%s", quiltImage, ver)
dockerOpts := ""
if MinionTLSDir != "" {
// Mount the TLSDir as a read-only host volume. This is necessary for
// the minion container to access the TLS certificates copied by
// the daemon onto the host machine.
dockerOpts = fmt.Sprintf("-v %[1]s:%[1]s:ro", MinionTLSDir)
}
// Mount the TLSDir as a read-only host volume. This is necessary for
// the minion container to access the TLS certificates copied by
// the daemon onto the host machine.
dockerOpts := fmt.Sprintf("-v %[1]s:%[1]s:ro", tlsIO.MinionTLSDir)
var cloudConfigBytes bytes.Buffer
err := t.Execute(&cloudConfigBytes, struct {
@@ -48,7 +43,7 @@ func Ubuntu(m db.Machine, inboundPublic string) string {
QuiltImage: img,
SSHKeys: strings.Join(m.SSHKeys, "\n"),
LogLevel: log.GetLevel().String(),
MinionOpts: minionOptions(m.Role, inboundPublic, MinionTLSDir),
MinionOpts: minionOptions(m.Role, inboundPublic),
DockerOpts: dockerOpts,
})
if err != nil {
@@ -58,16 +53,11 @@ func Ubuntu(m db.Machine, inboundPublic string) string {
return cloudConfigBytes.String()
}
func minionOptions(role db.Role, inboundPublic, tlsDir string) string {
func minionOptions(role db.Role, inboundPublic string) string {
options := fmt.Sprintf("--role %q", role)
if inboundPublic != "" {
options += fmt.Sprintf(" --inbound-pub-intf %q", inboundPublic)
}
if tlsDir != "" {
options += fmt.Sprintf(" --tls-dir %q", tlsDir)
}
return options
}
View
@@ -18,20 +18,21 @@ func TestCloudConfig(t *testing.T) {
SSHKeys: []string{"a", "b"},
Role: db.Master,
}, "")
exp := "(quilt/quilt:master) (a\nb) (--role \"Master\") (info) ()"
exp := "(quilt/quilt:master) (a\nb) (--role \"Master\") (info)" +
" (-v /home/quilt/.quilt/tls:/home/quilt/.quilt/tls:ro)"
if res != exp {
t.Errorf("res: %s\nexp: %s", res, exp)
}
log.SetLevel(log.DebugLevel)
ver = "1.2.3"
MinionTLSDir = "dir"
res = Ubuntu(db.Machine{
SSHKeys: []string{"a", "b"},
Role: db.Worker,
}, "ib")
exp = "(quilt/quilt:1.2.3) (a\nb) (--role \"Worker\" --inbound-pub-intf \"ib\"" +
" --tls-dir \"dir\") (debug) (-v dir:dir:ro)"
exp = "(quilt/quilt:1.2.3) (a\nb) (--role \"Worker\"" +
" --inbound-pub-intf \"ib\") (debug)" +
" (-v /home/quilt/.quilt/tls:/home/quilt/.quilt/tls:ro)"
if res != exp {
t.Errorf("res: %s\nexp: %s", res, exp)
}
View
@@ -8,7 +8,6 @@ import (
"github.com/quilt/quilt/blueprint"
"github.com/quilt/quilt/cloud/acl"
"github.com/quilt/quilt/cloud/amazon"
"github.com/quilt/quilt/cloud/cfg"
"github.com/quilt/quilt/cloud/digitalocean"
"github.com/quilt/quilt/cloud/foreman"
"github.com/quilt/quilt/cloud/google"
@@ -49,8 +48,7 @@ var sleep = time.Sleep
// Run continually checks 'conn' for cloud changes and recreates the cloud as
// needed.
func Run(conn db.Conn, creds connection.Credentials, minionTLSDir string) {
cfg.MinionTLSDir = minionTLSDir
func Run(conn db.Conn, creds connection.Credentials) {
foreman.Credentials = creds
go updateMachineStatuses(conn)
Oops, something went wrong.

0 comments on commit 239f742

Please sign in to comment.