Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mutual TLS Auth server and clients #1528

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ const (
)

const (
// ComponentAuthority is a TLS and an SSH certificate authority
ComponentAuthority = "authority"

// ComponentProcess is a main control process
ComponentProcess = "process"

// ComponentReverseTunnelServer is reverse tunnel server
// that together with agent establish a bi-directional SSH revers tunnel
// to bypass firewall restrictions
Expand Down Expand Up @@ -172,6 +178,16 @@ const (
// DirMaskSharedGroup is the mask for a directory accessible
// by the owner and group
DirMaskSharedGroup = 0770

// FileMaskOwnerOnly is the file mask that allows read write access
// to owers only
FileMaskOwnerOnly = 0600

// On means mode is on
On = "on"

// Off means mode is off
Off = "off"
)

const (
Expand Down Expand Up @@ -247,3 +263,6 @@ const AdminRoleName = "admin"
// DefaultImplicitRole is implicit role that gets added to all service.RoleSet
// objects.
const DefaultImplicitRole = "default-implicit-role"

// APIDomain is a default domain name for Auth server API
const APIDomain = "teleport.cluster.local"
5 changes: 4 additions & 1 deletion docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ FROM teleport-buildbox:latest

# DEBUG=1 is needed for the Web UI to be loaded from static assets instead
# of the binary
ENV DEBUG=1 GOPATH=/root/go PATH=$PATH:/root/go/src/github.com/gravitational/teleport/build
ENV DEBUG=1 GOPATH=/root/go PATH=$PATH:/root/go/src/github.com/gravitational/teleport/build:/root/go/bin

# htop is useful for testing terminal resizing
RUN apt-get install -y htop vim screen; \
Expand All @@ -13,6 +13,9 @@ RUN apt-get install -y htop vim screen; \
# allows ansible testing
RUN apt-get install -y ansible

# installs gops
RUN go get -u github.com/google/gops

VOLUME ["/teleport", "/var/lib/teleport"]
COPY .bashrc /root/.bashrc
COPY .screenrc /root/.screenrc
Expand Down
4 changes: 4 additions & 0 deletions docker/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ check-license:
mkdir -p ./data/two/auth && cp $(TEST_LICENSE) ./data/two/auth; \
fi

.PHONY: build
build:
docker build -t teleport:latest .

.PHONY: clean
clean:
rm -rf data
20 changes: 14 additions & 6 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ services:
one:
image: teleport:latest
container_name: one
command: ${CONTAINERHOME}/build/teleport start -d -c ${CONTAINERHOME}/docker/one.yaml
command: ${CONTAINERHOME}/build/teleport start -d -c ${CONTAINERHOME}/docker/one.yaml --gops --gops-addr=127.0.0.1:4321
mem_limit: 300m
memswap_limit: 0
ports:
- "3080:3080"
- "3023:3023"
Expand All @@ -27,9 +29,11 @@ services:
one-node:
image: teleport:latest
container_name: one-node
command: ${CONTAINERHOME}/build/teleport start -d -c ${CONTAINERHOME}/docker/one-node.yaml
command: ${CONTAINERHOME}/build/teleport start -d -c ${CONTAINERHOME}/docker/one-node.yaml --gops --gops-addr=127.0.0.1:4321
env_file: env.file
mem_limit: 300m
volumes:
- ./data/one-node:/var/lib/teleport
- ../:/root/go/src/github.com/gravitational/teleport
networks:
teleport:
Expand All @@ -41,7 +45,8 @@ services:
one-proxy:
image: teleport:latest
container_name: one-proxy
command: ${CONTAINERHOME}/build/teleport start -d -c ${CONTAINERHOME}/docker/one-proxy.yaml
command: ${CONTAINERHOME}/build/teleport start -d -c ${CONTAINERHOME}/docker/one-proxy.yaml --gops --gops-addr=127.0.0.1:4321
mem_limit: 300m
ports:
- "4080:3080"
- "4023:3023"
Expand All @@ -59,9 +64,10 @@ services:
# two-auth is a auth server of the second cluster
#
two-auth:
mem_limit: 300m
image: teleport:latest
container_name: two-auth
command: ${CONTAINERHOME}/build/teleport start -d -c ${CONTAINERHOME}/docker/two-auth.yaml --insecure
command: ${CONTAINERHOME}/build/teleport start -d -c ${CONTAINERHOME}/docker/two-auth.yaml --insecure --gops --gops-addr=127.0.0.1:4321
env_file: env.file
volumes:
- ./data/two/auth:/var/lib/teleport
Expand All @@ -74,9 +80,10 @@ services:
# two-proxy is a proxy service for the second cluster
#
two-proxy:
mem_limit: 300m
image: teleport:latest
container_name: two-proxy
command: ${CONTAINERHOME}/build/teleport start -d -c ${CONTAINERHOME}/docker/two-proxy.yaml
command: ${CONTAINERHOME}/build/teleport start -d -c ${CONTAINERHOME}/docker/two-proxy.yaml --gops --gops-addr=127.0.0.1:4321
env_file: env.file
ports:
- "5080:5080"
Expand All @@ -92,9 +99,10 @@ services:
# two-node is a node service for the second cluster
#
two-node:
mem_limit: 300m
image: teleport:latest
container_name: two-node
command: ${CONTAINERHOME}/build/teleport start -d -c ${CONTAINERHOME}/docker/two-node.yaml
command: ${CONTAINERHOME}/build/teleport start -d -c ${CONTAINERHOME}/docker/two-node.yaml --gops --gops-addr=127.0.0.1:4321
env_file: env.file
volumes:
- ./data/two/node:/var/lib/teleport
Expand Down
1 change: 1 addition & 0 deletions docker/one.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,5 @@ ssh_service:

proxy_service:
enabled: yes
proxy_protocol: on

2 changes: 1 addition & 1 deletion e
Submodule e updated from 4f0f10 to ac3bfc
60 changes: 48 additions & 12 deletions integration/helpers.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package integration

import (
"crypto/rsa"
"crypto/x509/pkix"
"encoding/json"
"fmt"
"io"
Expand All @@ -11,6 +13,7 @@ import (
"os/exec"
"os/user"
"path/filepath"
"runtime/debug"
"strconv"
"sync"
"time"
Expand All @@ -31,10 +34,11 @@ import (
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/sshutils"
"github.com/gravitational/teleport/lib/teleagent"
"github.com/gravitational/teleport/lib/tlsca"
"github.com/gravitational/teleport/lib/utils"

"github.com/gravitational/trace"

"github.com/jonboulle/clockwork"
log "github.com/sirupsen/logrus"
)

Expand Down Expand Up @@ -79,9 +83,16 @@ type InstanceSecrets struct {
// instance name (aka "site name")
SiteName string `json:"site_name"`
// instance keys+cert (reused for hostCA and userCA)
PubKey []byte `json:"pub"`
// PubKey is instance public key
PubKey []byte `json:"pub"`
// PrivKey is instance private key
PrivKey []byte `json:"priv"`
Cert []byte `json:"cert"`
// Cert is SSH host certificate
Cert []byte `json:"cert"`
// TLSCACert is the certificate of the trusted certificate authority
TLSCACert []byte `json:"tls_ca_cert"`
// TLSCert is client TLS X509 certificate
TLSCert []byte `json:"tls_cert"`
// ListenAddr is a reverse tunnel listening port, allowing
// other sites to connect to i instance. Set to empty
// string if i instance is not allowing incoming tunnels
Expand Down Expand Up @@ -112,6 +123,15 @@ func NewInstance(clusterName string, hostID string, nodeName string, ports []int
if priv == nil || pub == nil {
priv, pub, _ = keygen.GenerateKeyPair("")
}
rsaKey, err := ssh.ParseRawPrivateKey(priv)
fatalIf(err)

tlsCAKey, tlsCACert, err := tlsca.GenerateSelfSignedCAWithPrivateKey(rsaKey.(*rsa.PrivateKey), pkix.Name{
CommonName: clusterName,
Organization: []string{clusterName},
}, nil, defaults.CATTL)
fatalIf(err)

cert, err := keygen.GenerateHostCert(services.HostCertParams{
PrivateCASigningKey: priv,
PublicHostKey: pub,
Expand All @@ -122,6 +142,22 @@ func NewInstance(clusterName string, hostID string, nodeName string, ports []int
TTL: time.Duration(time.Hour * 24),
})
fatalIf(err)
tlsCA, err := tlsca.New(tlsCACert, tlsCAKey)
fatalIf(err)
cryptoPubKey, err := sshutils.CryptoPublicKey(pub)
identity := tlsca.Identity{
Username: fmt.Sprintf("%v.%v", hostID, clusterName),
Groups: []string{string(teleport.RoleAdmin)},
}
clock := clockwork.NewRealClock()
tlsCert, err := tlsCA.GenerateCertificate(tlsca.CertificateRequest{
Clock: clock,
PublicKey: cryptoPubKey,
Subject: identity.Subject(),
NotAfter: clock.Now().UTC().Add(time.Hour * 24),
})
fatalIf(err)

i := &TeleInstance{
Ports: ports,
Hostname: nodeName,
Expand All @@ -131,6 +167,8 @@ func NewInstance(clusterName string, hostID string, nodeName string, ports []int
PrivKey: priv,
PubKey: pub,
Cert: cert,
TLSCACert: tlsCACert,
TLSCert: tlsCert,
ListenAddr: net.JoinHostPort(nodeName, strconv.Itoa(ports[4])),
WebProxyAddr: net.JoinHostPort(nodeName, i.GetPortWeb()),
Users: make(map[string]*User),
Expand All @@ -157,8 +195,10 @@ func (s *InstanceSecrets) GetRoles() []services.Role {
// case we always return hard-coded userCA + hostCA (and they share keys
// for simplicity)
func (s *InstanceSecrets) GetCAs() []services.CertAuthority {
hostCA := services.NewCertAuthority(services.HostCA, s.SiteName, [][]byte{s.PrivKey}, [][]byte{s.PubKey}, []string{})
hostCA.SetTLSKeyPairs([]services.TLSKeyPair{{Cert: s.TLSCACert, Key: s.PrivKey}})
return []services.CertAuthority{
services.NewCertAuthority(services.HostCA, s.SiteName, [][]byte{s.PrivKey}, [][]byte{s.PubKey}, []string{}),
hostCA,
services.NewCertAuthority(services.UserCA, s.SiteName, [][]byte{s.PrivKey}, [][]byte{s.PubKey}, []string{services.RoleNameForCertAuthority(s.SiteName)}),
}
}
Expand Down Expand Up @@ -193,7 +233,7 @@ func (s *InstanceSecrets) AsSlice() []*InstanceSecrets {
}

func (s *InstanceSecrets) GetIdentity() *auth.Identity {
i, err := auth.ReadIdentityFromKeyPair(s.PrivKey, s.Cert)
i, err := auth.ReadIdentityFromKeyPair(s.PrivKey, s.Cert, s.TLSCert, s.TLSCACert)
fatalIf(err)
return i
}
Expand Down Expand Up @@ -366,11 +406,7 @@ func (i *TeleInstance) CreateEx(trustedSecrets []*InstanceSecrets, tconf *servic
}
// sign user's keys:
ttl := 24 * time.Hour
logins, err := services.RoleSet(roles).CheckLoginDuration(ttl)
if err != nil {
return trace.Wrap(err)
}
user.Key.Cert, err = auth.GenerateUserCert(user.Key.Pub, teleUser, logins, ttl, true, true, teleport.CompatibilityNone)
user.Key.Cert, user.Key.TLSCert, err = auth.GenerateUserCerts(user.Key.Pub, teleUser.GetName(), ttl, teleport.CompatibilityNone)
if err != nil {
return err
}
Expand Down Expand Up @@ -663,7 +699,7 @@ func (i *TeleInstance) NewClient(cfg ClientConfig) (tc *client.TeleportClient, e
// equivalent of 'known hosts' in openssh
cas := i.Secrets.GetCAs()
for i := range cas {
err = tc.AddTrustedCA(cas[i].V1())
err = tc.AddTrustedCA(cas[i])
if err != nil {
return nil, trace.Wrap(err)
}
Expand Down Expand Up @@ -974,7 +1010,7 @@ func closeAgent(teleAgent *teleagent.AgentServer, socketDirPath string) error {

func fatalIf(err error) {
if err != nil {
log.Fatal("", err)
log.Fatalf("%v at %v", string(debug.Stack()), err)
}
}

Expand Down
3 changes: 2 additions & 1 deletion integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -709,7 +709,8 @@ func (s *IntSuite) TestTwoClusters(c *check.C) {
// Stop "site-A" and try to connect to it again via "site-A" (expect a connection error)
a.Stop(false)
err = tc.SSH(context.TODO(), cmd, false)
c.Assert(err, check.ErrorMatches, `failed connecting to node localhost. site-A is offline`)
// debug mode will add more lines, so this check has to be flexible
c.Assert(strings.Replace(err.Error(), "\n", "", -1), check.Matches, `.*site-A is offline.*`)

// Reset and start "Site-A" again
a.Reset()
Expand Down
3 changes: 3 additions & 0 deletions lib/auth/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ type AccessPoint interface {
// GetProxies returns a list of proxy servers registered in the cluster
GetProxies() ([]services.Server, error)

// GetCertAuthority returns cert authority by id
GetCertAuthority(id services.CertAuthID, loadKeys bool) (services.CertAuthority, error)

// GetCertAuthorities returns a list of cert authorities
GetCertAuthorities(caType services.CertAuthType, loadKeys bool) ([]services.CertAuthority, error)

Expand Down
Loading