Introduce auth server and proxy heartbeats

This commit introduces heartbeats of AuthServers and Proxies and fixes several issues:

1. Server init problem

There was an issue in server init, when certificates of multiple roles were overwriting each otther.
Now Teleport stores each keypair and certificate in a separate file <hostid>.role.key and <hostid>.role.cert
This also means that it's backwards incompatible with previous on disk format.

2. Proxy and Auth heartbeats

Auth servers and proxies now heartbeat into cluster as well

3. Bugfixes:

* Proxy role was missing, it is now treated as a separate role with permissions
* AdvertiseIP is now a global setting that can be used by all roles
* --advertise-ip flag was ignored and was never applied
* teleport service initialization has been simplified, now each role get it's own client
* minor cleanups
This commit is contained in:
klizhentas 2016-03-11 20:09:40 -08:00
parent a8f419a0ad
commit c1e0604dd0
38 changed files with 786 additions and 495 deletions

View file

@ -2,20 +2,20 @@
# This Makefile is used for CI/CD builds. Please be careful!
#
BBOX=teleport-buildbox:latest
OUT="../"
OUT="../out"
#
# builds 'teleport' binary and places it $(OUT) dir
#
build: bbox
docker run -i --rm=true \
-v $$(pwd)/../:/gopath/src/github.com/gravitational/teleport \
-v "$$(pwd)/../":/gopath/src/github.com/gravitational/teleport \
-u $$(id -u):$$(id -g) \
$(BBOX) \
/bin/bash -c "make -C /gopath/src/github.com/gravitational/teleport FLAGS='-cover -race' clean test all"
@echo "\nSUCCESS ----> $(OUT)teleport"
@echo "SUCCESS ----> $(OUT)tctl"
@echo "SUCCESS ----> $(OUT)tsh"
@echo "\nSUCCESS ----> $(OUT)/teleport"
@echo "SUCCESS ----> $(OUT)/tctl"
@echo "SUCCESS ----> $(OUT)/tsh"
#
# builds buildbox:1.0.0 Docker container which is Debian8 with Golang 1.4.2

View file

@ -317,6 +317,12 @@ func ConvertSystemError(err error) error {
if terr, ok := err.(trace.Error); ok {
innerError = terr.OrigError()
}
if os.IsNotExist(err) {
return NotFound(innerError.Error())
}
if os.IsPermission(err) {
return AccessDenied(innerError.Error())
}
switch realErr := innerError.(type) {
case *net.OpError:
return ConnectionProblem(

View file

@ -32,11 +32,15 @@ type AccessPoint interface {
GetLocalDomain() (string, error)
// GetServers returns a list of registered servers
GetServers() ([]services.Server, error)
GetNodes() ([]services.Server, error)
// UpsertServer registers server presence, permanently if ttl is 0 or
// for the specified duration with second resolution if it's >= 1 second
UpsertServer(s services.Server, ttl time.Duration) error
UpsertNode(s services.Server, ttl time.Duration) error
// UpsertProxy registers server presence, permanently if ttl is 0 or
// for the specified duration with second resolution if it's >= 1 second
UpsertProxy(s services.Server, ttl time.Duration) error
// GetCertAuthorities returns a list of cert authorities
GetCertAuthorities(caType services.CertAuthType) ([]*services.CertAuthority, error)

View file

@ -36,26 +36,34 @@ import (
)
type APIWithRoles struct {
config APIConfig
listeners map[teleport.Role]*fakeSocket
servers map[teleport.Role]*APIServer
}
func NewAPIWithRoles(authServer *AuthServer, elog events.Log,
sessions session.Service, recorder recorder.Recorder,
permChecker PermissionChecker,
roles []teleport.Role) *APIWithRoles {
// APIConfig is a configuration file
type APIConfig struct {
AuthServer *AuthServer
EventLog events.Log
SessionService session.Service
Recorder recorder.Recorder
Roles []teleport.Role
PermissionChecker PermissionChecker
}
func NewAPIWithRoles(config APIConfig) *APIWithRoles {
api := APIWithRoles{}
api.listeners = make(map[teleport.Role]*fakeSocket)
api.servers = make(map[teleport.Role]*APIServer)
for _, role := range roles {
for _, role := range config.Roles {
a := AuthWithRoles{
authServer: authServer,
elog: elog,
sessions: sessions,
recorder: recorder,
permChecker: permChecker,
authServer: config.AuthServer,
elog: config.EventLog,
sessions: config.SessionService,
recorder: config.Recorder,
permChecker: config.PermissionChecker,
role: role,
}
api.servers[role] = NewAPIServer(&a)
@ -66,7 +74,7 @@ func NewAPIWithRoles(authServer *AuthServer, elog events.Log,
func (api *APIWithRoles) Serve() {
wg := sync.WaitGroup{}
for role, _ := range api.listeners {
for role := range api.listeners {
wg.Add(1)
go func(listener net.Listener, handler http.Handler) {
if err := http.Serve(listener, handler); (err != nil) && (err != io.EOF) {

View file

@ -32,7 +32,6 @@ import (
"github.com/gravitational/teleport/lib/utils"
log "github.com/Sirupsen/logrus"
"github.com/codahale/lunk"
"github.com/gravitational/trace"
"github.com/julienschmidt/httprouter"
@ -91,9 +90,12 @@ func NewAPIServer(a *AuthWithRoles) *APIServer {
srv.POST("/v1/signuptokens", httplib.MakeHandler(srv.createSignupToken))
// Servers and presence heartbeat
srv.POST("/v1/servers", httplib.MakeHandler(srv.upsertServer))
srv.GET("/v1/servers", httplib.MakeHandler(srv.getServers))
srv.GET("/v1/auth/servers", httplib.MakeHandler(srv.getAuthServers))
srv.POST("/v1/nodes", httplib.MakeHandler(srv.upsertNode))
srv.GET("/v1/nodes", httplib.MakeHandler(srv.getNodes))
srv.POST("/v1/authservers", httplib.MakeHandler(srv.upsertAuthServer))
srv.GET("/v1/authservers", httplib.MakeHandler(srv.getAuthServers))
srv.POST("/v1/proxies", httplib.MakeHandler(srv.upsertProxy))
srv.GET("/v1/proxies", httplib.MakeHandler(srv.getProxies))
// Tokens
srv.POST("/v1/tokens", httplib.MakeHandler(srv.generateToken))
@ -127,33 +129,69 @@ type upsertServerReq struct {
TTL time.Duration `json:"ttl"`
}
// upsertServer is called by remote SSH nodes when they ping back into the auth service
func (s *APIServer) upsertServer(w http.ResponseWriter, r *http.Request, p httprouter.Params) (interface{}, error) {
// upsertNode is called by remote SSH nodes when they ping back into the auth service
func (s *APIServer) upsertServer(role teleport.Role, w http.ResponseWriter, r *http.Request, p httprouter.Params) (interface{}, error) {
var req upsertServerReq
if err := httplib.ReadJSON(r, &req); err != nil {
return nil, trace.Wrap(err)
}
log.Debugf("[AUTH API] ping from %v (%v) at %v", req.Server.ID, req.Server.Hostname, r.RemoteAddr)
log.Debugf("[AUTH API] ping from %v %v (%v) at %v", role, req.Server.ID, req.Server.Hostname, r.RemoteAddr)
// if server sent "local" IP address to us, replace the ip/host part with the remote address we see
// on the socket, but keep the original port:
req.Server.Addr = utils.ReplaceLocalhost(req.Server.Addr, r.RemoteAddr)
if err := s.a.UpsertServer(req.Server, req.TTL); err != nil {
log.Error(err)
return nil, trace.Wrap(err)
switch role {
case teleport.RoleNode:
if err := s.a.UpsertNode(req.Server, req.TTL); err != nil {
return nil, trace.Wrap(err)
}
case teleport.RoleAuth:
if err := s.a.UpsertAuthServer(req.Server, req.TTL); err != nil {
return nil, trace.Wrap(err)
}
case teleport.RoleProxy:
if err := s.a.UpsertProxy(req.Server, req.TTL); err != nil {
return nil, trace.Wrap(err)
}
}
return message("ok"), nil
}
func (s *APIServer) getServers(w http.ResponseWriter, r *http.Request, p httprouter.Params) (interface{}, error) {
servers, err := s.a.GetServers()
// upsertNode is called by remote SSH nodes when they ping back into the auth service
func (s *APIServer) upsertNode(w http.ResponseWriter, r *http.Request, p httprouter.Params) (interface{}, error) {
return s.upsertServer(teleport.RoleNode, w, r, p)
}
// getNodes returns registered SSH nodes
func (s *APIServer) getNodes(w http.ResponseWriter, r *http.Request, p httprouter.Params) (interface{}, error) {
servers, err := s.a.GetNodes()
if err != nil {
return nil, trace.Wrap(err)
}
return servers, nil
}
// upsertProxy is called by remote SSH nodes when they ping back into the auth service
func (s *APIServer) upsertProxy(w http.ResponseWriter, r *http.Request, p httprouter.Params) (interface{}, error) {
return s.upsertServer(teleport.RoleProxy, w, r, p)
}
// getProxies returns registered proxies
func (s *APIServer) getProxies(w http.ResponseWriter, r *http.Request, p httprouter.Params) (interface{}, error) {
servers, err := s.a.GetProxies()
if err != nil {
return nil, trace.Wrap(err)
}
return servers, nil
}
// upsertAuthServer is called by remote Auth servers when they ping back into the auth service
func (s *APIServer) upsertAuthServer(w http.ResponseWriter, r *http.Request, p httprouter.Params) (interface{}, error) {
return s.upsertServer(teleport.RoleAuth, w, r, p)
}
// getAuthServers returns registered auth servers
func (s *APIServer) getAuthServers(w http.ResponseWriter, r *http.Request, p httprouter.Params) (interface{}, error) {
servers, err := s.a.GetAuthServers()
if err != nil {

View file

@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package auth
import (
@ -272,27 +273,47 @@ func (s *APISuite) TestSessions(c *C) {
}
func (s *APISuite) TestServers(c *C) {
out, err := s.clt.GetServers()
out, err := s.clt.GetNodes()
c.Assert(err, IsNil)
c.Assert(len(out), Equals, 0)
srv := services.Server{ID: "id1", Addr: "host:1233", Hostname: "host1"}
c.Assert(s.clt.UpsertServer(srv, 0), IsNil)
c.Assert(s.clt.UpsertNode(srv, 0), IsNil)
srv1 := services.Server{ID: "id2", Addr: "host:1234", Hostname: "host2"}
c.Assert(s.clt.UpsertServer(srv1, 0), IsNil)
c.Assert(s.clt.UpsertNode(srv1, 0), IsNil)
out, err = s.clt.GetServers()
out, err = s.clt.GetNodes()
c.Assert(err, IsNil)
c.Assert(out, DeepEquals, []services.Server{srv, srv1})
// note: ID is getting overwritten by addr:
if out[0].ID == "id1" {
c.Assert(out[0], DeepEquals, services.Server{ID: "id1", Addr: "host:1233", Hostname: "host1"})
c.Assert(out[1], DeepEquals, services.Server{ID: "id2", Addr: "host:1234", Hostname: "host2"})
} else {
c.Assert(out[1], DeepEquals, services.Server{ID: "id1", Addr: "host:1233", Hostname: "host1"})
c.Assert(out[0], DeepEquals, services.Server{ID: "id2", Addr: "host:1234", Hostname: "host2"})
}
out, err = s.clt.GetProxies()
c.Assert(err, IsNil)
c.Assert(len(out), Equals, 0)
srv = services.Server{ID: "proxy1", Addr: "host:1233", Hostname: "host1"}
c.Assert(s.clt.UpsertProxy(srv, 0), IsNil)
srv1 = services.Server{ID: "proxy2", Addr: "host:1234", Hostname: "host2"}
c.Assert(s.clt.UpsertProxy(srv1, 0), IsNil)
out, err = s.clt.GetProxies()
c.Assert(err, IsNil)
c.Assert(out, DeepEquals, []services.Server{srv, srv1})
out, err = s.clt.GetAuthServers()
c.Assert(err, IsNil)
c.Assert(len(out), Equals, 0)
srv = services.Server{ID: "auth1", Addr: "host:1233", Hostname: "host1"}
c.Assert(s.clt.UpsertAuthServer(srv, 0), IsNil)
srv1 = services.Server{ID: "auth2", Addr: "host:1234", Hostname: "host2"}
c.Assert(s.clt.UpsertAuthServer(srv1, 0), IsNil)
out, err = s.clt.GetAuthServers()
c.Assert(err, IsNil)
c.Assert(out, DeepEquals, []services.Server{srv, srv1})
}
func (s *APISuite) TestEvents(c *C) {

View file

@ -238,30 +238,12 @@ func (s *AuthServer) ValidateToken(token string) (role string, e error) {
return tok.Role, nil
}
func (s *AuthServer) RegisterUsingToken(outputToken, hostID string, role teleport.Role) (keys PackedKeys, e error) {
log.Infof("[AUTH] Node `%v` is trying to join", hostID)
if hostID == "" {
return PackedKeys{}, trace.Wrap(fmt.Errorf("HostID cannot be empty"))
}
if err := role.Check(); err != nil {
return PackedKeys{}, trace.Wrap(err)
}
token, _, err := services.SplitTokenRole(outputToken)
if err != nil {
return PackedKeys{}, trace.Wrap(err)
}
tok, err := s.ProvisioningService.GetToken(token)
if err != nil {
log.Warningf("[AUTH] Node `%v` cannot join: token error. %v", hostID, err)
return PackedKeys{}, trace.Wrap(err)
}
if tok.Role != string(role) {
return PackedKeys{}, trace.Wrap(
teleport.BadParameter("token.Role", "role does not match"))
}
// GenerateServerKeys generates private key and certificate signed
// by the host certificate authority, listing the role of this server
func (s *AuthServer) GenerateServerKeys(hostID string, role teleport.Role) (*PackedKeys, error) {
k, pub, err := s.GenerateKeyPair("")
if err != nil {
return PackedKeys{}, trace.Wrap(err)
return nil, trace.Wrap(err)
}
// we always append authority's domain to resulting node name,
// that's how we make sure that nodes are uniquely identified/found
@ -270,18 +252,43 @@ func (s *AuthServer) RegisterUsingToken(outputToken, hostID string, role telepor
c, err := s.GenerateHostCert(pub, fqdn, s.DomainName, role, 0)
if err != nil {
log.Warningf("[AUTH] Node `%v` cannot join: cert generation error. %v", hostID, err)
return PackedKeys{}, trace.Wrap(err)
return nil, trace.Wrap(err)
}
keys = PackedKeys{
return &PackedKeys{
Key: k,
Cert: c,
}
}, nil
}
func (s *AuthServer) RegisterUsingToken(outputToken, hostID string, role teleport.Role) (*PackedKeys, error) {
log.Infof("[AUTH] Node `%v` is trying to join", hostID)
if hostID == "" {
return nil, trace.Wrap(fmt.Errorf("HostID cannot be empty"))
}
if err := role.Check(); err != nil {
return nil, trace.Wrap(err)
}
token, _, err := services.SplitTokenRole(outputToken)
if err != nil {
return nil, trace.Wrap(err)
}
tok, err := s.ProvisioningService.GetToken(token)
if err != nil {
log.Warningf("[AUTH] Node `%v` cannot join: token error. %v", hostID, err)
return nil, trace.Wrap(err)
}
if tok.Role != string(role) {
return nil, trace.Wrap(
teleport.BadParameter("token.Role", "role does not match"))
}
keys, err := s.GenerateServerKeys(hostID, role)
if err != nil {
return nil, trace.Wrap(err)
}
if err := s.DeleteToken(outputToken); err != nil {
return PackedKeys{}, trace.Wrap(err)
return nil, trace.Wrap(err)
}
utils.Consolef(os.Stdout, "[AUTH] Node `%v` joined the cluster", hostID)
return keys, nil
}

View file

@ -25,8 +25,6 @@ import (
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/session"
log "github.com/Sirupsen/logrus"
"github.com/codahale/lunk"
"github.com/gravitational/trace"
)
@ -127,9 +125,9 @@ func (a *AuthWithRoles) GenerateToken(role teleport.Role, ttl time.Duration) (st
return a.authServer.GenerateToken(role, ttl)
}
}
func (a *AuthWithRoles) RegisterUsingToken(token, hostID string, role teleport.Role) (keys PackedKeys, e error) {
func (a *AuthWithRoles) RegisterUsingToken(token, hostID string, role teleport.Role) (*PackedKeys, error) {
if err := a.permChecker.HasPermission(a.role, ActionRegisterUsingToken); err != nil {
return PackedKeys{}, trace.Wrap(err)
return nil, trace.Wrap(err)
} else {
return a.authServer.RegisterUsingToken(token, hostID, role)
}
@ -190,19 +188,25 @@ func (a *AuthWithRoles) GetChunkReader(id string) (recorder.ChunkReadCloser, err
return a.recorder.GetChunkReader(id)
}
}
func (a *AuthWithRoles) UpsertServer(s services.Server, ttl time.Duration) error {
func (a *AuthWithRoles) UpsertNode(s services.Server, ttl time.Duration) error {
if err := a.permChecker.HasPermission(a.role, ActionUpsertServer); err != nil {
log.Error(err)
return trace.Wrap(err)
} else {
return a.authServer.UpsertServer(s, ttl)
return a.authServer.UpsertNode(s, ttl)
}
}
func (a *AuthWithRoles) GetServers() ([]services.Server, error) {
func (a *AuthWithRoles) GetNodes() ([]services.Server, error) {
if err := a.permChecker.HasPermission(a.role, ActionGetServers); err != nil {
return nil, trace.Wrap(err)
} else {
return a.authServer.GetServers()
return a.authServer.GetNodes()
}
}
func (a *AuthWithRoles) UpsertAuthServer(s services.Server, ttl time.Duration) error {
if err := a.permChecker.HasPermission(a.role, ActionUpsertAuthServer); err != nil {
return trace.Wrap(err)
} else {
return a.authServer.UpsertAuthServer(s, ttl)
}
}
func (a *AuthWithRoles) GetAuthServers() ([]services.Server, error) {
@ -212,6 +216,20 @@ func (a *AuthWithRoles) GetAuthServers() ([]services.Server, error) {
return a.authServer.GetAuthServers()
}
}
func (a *AuthWithRoles) UpsertProxy(s services.Server, ttl time.Duration) error {
if err := a.permChecker.HasPermission(a.role, ActionUpsertProxy); err != nil {
return trace.Wrap(err)
} else {
return a.authServer.UpsertProxy(s, ttl)
}
}
func (a *AuthWithRoles) GetProxies() ([]services.Server, error) {
if err := a.permChecker.HasPermission(a.role, ActionGetProxies); err != nil {
return nil, err
} else {
return a.authServer.GetProxies()
}
}
func (a *AuthWithRoles) UpsertPassword(user string, password []byte) (hotpURL string, hotpQR []byte, err error) {
if err := a.permChecker.HasPermission(a.role, ActionUpsertPassword); err != nil {
return "", nil, err

View file

@ -226,7 +226,7 @@ func (c *Client) GenerateToken(role teleport.Role, ttl time.Duration) (string, e
// RegisterUserToken calls the auth service API to register a new node via registration token
// which has been previously issued via GenerateToken
func (c *Client) RegisterUsingToken(token, hostID string, role teleport.Role) (PackedKeys, error) {
func (c *Client) RegisterUsingToken(token, hostID string, role teleport.Role) (*PackedKeys, error) {
out, err := c.PostJSON(c.Endpoint("tokens", "register"),
registerUsingTokenReq{
HostID: hostID,
@ -234,13 +234,13 @@ func (c *Client) RegisterUsingToken(token, hostID string, role teleport.Role) (P
Role: role,
})
if err != nil {
return PackedKeys{}, trace.Wrap(err)
return nil, trace.Wrap(err)
}
var keys PackedKeys
if err := json.Unmarshal(out.Bytes(), &keys); err != nil {
return PackedKeys{}, trace.Wrap(err)
return nil, trace.Wrap(err)
}
return keys, nil
return &keys, nil
}
func (c *Client) RegisterNewAuthServer(token string) error {
@ -306,20 +306,20 @@ func (c *Client) GetChunkReader(id string) (recorder.ChunkReadCloser, error) {
return &chunkRW{c: c, id: id}, nil
}
// UpsertServer is used by SSH servers to reprt their presense
// UpsertNode is used by SSH servers to reprt their presense
// to the auth servers in form of hearbeat expiring after ttl period.
func (c *Client) UpsertServer(s services.Server, ttl time.Duration) error {
func (c *Client) UpsertNode(s services.Server, ttl time.Duration) error {
args := upsertServerReq{
Server: s,
TTL: ttl,
}
_, err := c.PostJSON(c.Endpoint("servers"), args)
_, err := c.PostJSON(c.Endpoint("nodes"), args)
return trace.Wrap(err)
}
// GetServers returns the list of servers registered in the cluster.
func (c *Client) GetServers() ([]services.Server, error) {
out, err := c.Get(c.Endpoint("servers"), url.Values{})
// GetNodes returns the list of servers registered in the cluster.
func (c *Client) GetNodes() ([]services.Server, error) {
out, err := c.Get(c.Endpoint("nodes"), url.Values{})
if err != nil {
return nil, trace.Wrap(err)
}
@ -330,9 +330,44 @@ func (c *Client) GetServers() ([]services.Server, error) {
return re, nil
}
// UpsertAuthServer is used by auth servers to report their presense
// to other auth servers in form of hearbeat expiring after ttl period.
func (c *Client) UpsertAuthServer(s services.Server, ttl time.Duration) error {
args := upsertServerReq{
Server: s,
TTL: ttl,
}
_, err := c.PostJSON(c.Endpoint("authservers"), args)
return trace.Wrap(err)
}
// GetAuthServers returns the list of auth servers registered in the cluster.
func (c *Client) GetAuthServers() ([]services.Server, error) {
out, err := c.Get(c.Endpoint("auth", "servers"), url.Values{})
out, err := c.Get(c.Endpoint("authservers"), url.Values{})
if err != nil {
return nil, trace.Wrap(err)
}
var re []services.Server
if err := json.Unmarshal(out.Bytes(), &re); err != nil {
return nil, trace.Wrap(err)
}
return re, nil
}
// UpsertProxy is used by proxies to report their presense
// to other auth servers in form of hearbeat expiring after ttl period.
func (c *Client) UpsertProxy(s services.Server, ttl time.Duration) error {
args := upsertServerReq{
Server: s,
TTL: ttl,
}
_, err := c.PostJSON(c.Endpoint("proxies"), args)
return trace.Wrap(err)
}
// GetProxies returns the list of auth servers registered in the cluster.
func (c *Client) GetProxies() ([]services.Server, error) {
out, err := c.Get(c.Endpoint("proxies"), url.Values{})
if err != nil {
return nil, trace.Wrap(err)
}
@ -640,7 +675,7 @@ type ClientI interface {
GetCertAuthorities(caType services.CertAuthType) ([]*services.CertAuthority, error)
DeleteCertAuthority(caType services.CertAuthID) error
GenerateToken(role teleport.Role, ttl time.Duration) (string, error)
RegisterUsingToken(token, hostID string, role teleport.Role) (keys PackedKeys, e error)
RegisterUsingToken(token, hostID string, role teleport.Role) (*PackedKeys, error)
RegisterNewAuthServer(token string) error
Log(id lunk.EventID, e lunk.Event)
LogEntry(en lunk.Entry) error
@ -649,8 +684,8 @@ type ClientI interface {
GetSessionEvents(filter events.Filter) ([]session.Session, error)
GetChunkWriter(id string) (recorder.ChunkWriteCloser, error)
GetChunkReader(id string) (recorder.ChunkReadCloser, error)
UpsertServer(s services.Server, ttl time.Duration) error
GetServers() ([]services.Server, error)
UpsertNode(s services.Server, ttl time.Duration) error
GetNodes() ([]services.Server, error)
GetAuthServers() ([]services.Server, error)
UpsertPassword(user string, password []byte) (hotpURL string, hotpQR []byte, err error)
CheckPassword(user string, password []byte, hotpToken string) error

View file

@ -23,7 +23,7 @@ import (
)
type limitedClient interface {
GetServers() ([]services.Server, error)
GetNodes() ([]services.Server, error)
GetCertAuthorities(caType services.CertAuthType) ([]*services.CertAuthority, error)
}
@ -45,7 +45,7 @@ func RetryingClient(client limitedClient, retries int) *retryingClient {
func (c *retryingClient) GetServers() ([]services.Server, error) {
var e error
for i := 0; i < c.retries; i++ {
servers, err := c.limitedClient.GetServers()
servers, err := c.limitedClient.GetNodes()
if err == nil {
return servers, nil
}

View file

@ -33,10 +33,14 @@ import (
"golang.org/x/crypto/ssh"
)
// InitConfig is auth server init config
type InitConfig struct {
Backend backend.Backend
Authority Authority
// HostUUID is a UUID of this host
HostUUID string
// DomainName stores the FQDN of the signing CA (its certificate will have this
// name embedded). It is usually set to the GUID of the host the Auth service runs on
DomainName string
@ -59,9 +63,13 @@ type InitConfig struct {
}
// Init instantiates and configures an instance of AuthServer
func Init(cfg InitConfig) (*AuthServer, ssh.Signer, error) {
func Init(cfg InitConfig) (*AuthServer, *Identity, error) {
if cfg.DataDir == "" {
return nil, nil, fmt.Errorf("path can not be empty")
return nil, nil, trace.Wrap(teleport.BadParameter("data_dir", "data dir can not be empty"))
}
if cfg.HostUUID == "" {
return nil, nil, trace.Wrap(teleport.BadParameter("HostUUID", "host UUID can not be empty"))
}
err := os.MkdirAll(cfg.DataDir, os.ModeDir|0777)
@ -152,17 +160,17 @@ func Init(cfg InitConfig) (*AuthServer, ssh.Signer, error) {
}
}
signer, err := InitKeys(asrv, cfg.DataDir)
identity, err := initKeys(asrv, cfg.DataDir, IdentityID{HostUUID: cfg.HostUUID, Role: teleport.RoleAdmin})
if err != nil {
return nil, nil, err
}
return asrv, signer, nil
return asrv, identity, nil
}
// InitKeys initializes this node's host certificate signed by host authority
func InitKeys(a *AuthServer, dataDir string) (ssh.Signer, error) {
kp, cp := keysPath(dataDir)
// initKeys initializes this node's host certificate signed by host authority
func initKeys(a *AuthServer, dataDir string, id IdentityID) (*Identity, error) {
kp, cp := keysPath(dataDir, id)
keyExists, err := pathExists(kp)
if err != nil {
@ -175,29 +183,29 @@ func InitKeys(a *AuthServer, dataDir string) (ssh.Signer, error) {
}
if !keyExists || !certExists {
k, pub, err := a.GenerateKeyPair("")
privateKey, publicKey, err := a.GenerateKeyPair("")
if err != nil {
return nil, err
return nil, trace.Wrap(err)
}
c, err := a.GenerateHostCert(pub, a.DomainName, a.DomainName, teleport.RoleAdmin, 0)
cert, err := a.GenerateHostCert(publicKey, id.HostUUID, a.DomainName, id.Role, 0)
if err != nil {
return nil, err
return nil, trace.Wrap(err)
}
if err := writeKeys(dataDir, k, c); err != nil {
return nil, err
if err := writeKeys(dataDir, id, privateKey, cert); err != nil {
return nil, trace.Wrap(err)
}
}
i, err := ReadIdentity(dataDir)
i, err := ReadIdentity(dataDir, id)
if err != nil {
return nil, trace.Wrap(err)
}
return i.KeySigner, nil
return i, nil
}
// writeKeys saves the key/cert pair for a given domain onto disk. This usually means the
// domain trusts us (signed our public key)
func writeKeys(dataDir string, key []byte, cert []byte) error {
kp, cp := keysPath(dataDir)
func writeKeys(dataDir string, id IdentityID, key []byte, cert []byte) error {
kp, cp := keysPath(dataDir, id)
log.Debugf("write key to %v, cert from %v", kp, cp)
if err := ioutil.WriteFile(kp, key, 0600); err != nil {
@ -209,6 +217,7 @@ func writeKeys(dataDir string, key []byte, cert []byte) error {
return nil
}
// Identity is a collection of certificates and signers that represent identity
type Identity struct {
KeyBytes []byte
CertBytes []byte
@ -217,13 +226,19 @@ type Identity struct {
Cert *ssh.Certificate
}
// IdentityID is a combination of role and host UUID
type IdentityID struct {
Role teleport.Role
HostUUID string
}
// ReadIdentity reads, parses and returns the given pub/pri key + cert from the
// key storage (dataDir).
func ReadIdentity(dataDir string) (i *Identity, err error) {
kp, cp := keysPath(dataDir)
func ReadIdentity(dataDir string, id IdentityID) (i *Identity, err error) {
kp, cp := keysPath(dataDir, id)
log.Debugf("host identity: [key: %v, cert: %v]", kp, cp)
i = new(Identity)
i = &Identity{}
i.KeyBytes, err = utils.ReadPath(kp)
if err != nil {
@ -260,8 +275,8 @@ func ReadIdentity(dataDir string) (i *Identity, err error) {
}
// HaveHostKeys checks either the host keys are in place
func HaveHostKeys(dataDir string) (bool, error) {
kp, cp := keysPath(dataDir)
func HaveHostKeys(dataDir string, id IdentityID) (bool, error) {
kp, cp := keysPath(dataDir, id)
exists, err := pathExists(kp)
if !exists || err != nil {
@ -277,9 +292,9 @@ func HaveHostKeys(dataDir string) (bool, error) {
}
// keysPath returns two full file paths: to the host.key and host.cert
func keysPath(dataDir string) (key string, cert string) {
return filepath.Join(dataDir, "host.key"),
filepath.Join(dataDir, "host.cert")
func keysPath(dataDir string, id IdentityID) (key string, cert string) {
return filepath.Join(dataDir, fmt.Sprintf("host.%v.%v.key", id.HostUUID, string(id.Role))),
filepath.Join(dataDir, fmt.Sprintf("host.%v.%v.cert", id.HostUUID, string(id.Role)))
}
func pathExists(path string) (bool, error) {

View file

@ -38,6 +38,11 @@ func NewStandardPermissions() PermissionChecker {
sp := standardPermissions{}
sp.permissions = make(map[teleport.Role](map[string]bool))
sp.permissions[teleport.RoleAuth] = map[string]bool{
ActionUpsertAuthServer: true,
ActionGetAuthServers: true,
}
sp.permissions[teleport.RoleUser] = map[string]bool{
ActionSignIn: true,
ActionCreateWebSession: true,
@ -55,11 +60,13 @@ func NewStandardPermissions() PermissionChecker {
sp.permissions[teleport.RoleNode] = map[string]bool{
ActionUpsertServer: true,
ActionGetServers: true,
ActionGetProxies: true,
ActionGetAuthServers: true,
ActionGetCertAuthorities: true,
ActionGetUsers: true,
ActionGetLocalDomain: true,
ActionGetUserKeys: true,
ActionGetServers: true,
ActionUpsertParty: true,
ActionUpsertSession: true,
ActionLogEntry: true,
@ -68,6 +75,20 @@ func NewStandardPermissions() PermissionChecker {
ActionGetSessions: true,
}
sp.permissions[teleport.RoleProxy] = map[string]bool{
ActionGetServers: true,
ActionUpsertProxy: true,
ActionGetProxies: true,
ActionGetAuthServers: true,
ActionGetCertAuthorities: true,
ActionGetUsers: true,
ActionGetLocalDomain: true,
ActionGetUserKeys: true,
ActionLogEntry: true,
ActionGetSession: true,
ActionGetSessions: true,
}
sp.permissions[teleport.RoleWeb] = map[string]bool{
ActionUpsertSession: true,
ActionCreateWebSession: true,
@ -86,54 +107,6 @@ func NewStandardPermissions() PermissionChecker {
return &sp
}
// NewHangoutPermissions is a set of permissions allowed to various
// roles when auth server is started in hangout mode on user's computer
func NewHangoutPermissions() PermissionChecker {
sp := standardPermissions{}
sp.permissions = make(map[teleport.Role](map[string]bool))
sp.permissions[teleport.RoleUser] = map[string]bool{
ActionGenerateUserCert: true,
ActionGetCertAuthorities: true,
}
sp.permissions[teleport.RoleProvisionToken] = map[string]bool{
ActionRegisterUsingToken: true,
ActionRegisterNewAuthServer: true,
ActionGenerateUserCert: true,
}
sp.permissions[teleport.RoleHangoutRemoteUser] = map[string]bool{
ActionGenerateUserCert: true,
}
sp.permissions[teleport.RoleNode] = map[string]bool{
ActionUpsertServer: true,
ActionGetCertAuthorities: true,
ActionGetLocalDomain: true,
ActionGetUserKeys: true,
ActionGetServers: true,
ActionUpsertParty: true,
ActionLogEntry: true,
ActionGetChunkWriter: true,
ActionUpsertCertAuthority: true,
ActionUpsertSession: true,
ActionGetAuthServers: true,
}
sp.permissions[teleport.RoleWeb] = map[string]bool{
ActionGetWebSession: true,
ActionDeleteWebSession: true,
}
sp.permissions[teleport.RoleSignup] = map[string]bool{
ActionGetSignupTokenData: true,
ActionCreateUserWithToken: true,
}
return &sp
}
type standardPermissions struct {
permissions map[teleport.Role](map[string]bool)
}
@ -174,17 +147,12 @@ var StandardRoles = []teleport.Role{
teleport.RoleUser,
teleport.RoleWeb,
teleport.RoleNode,
teleport.RoleProxy,
teleport.RoleAdmin,
teleport.RoleProvisionToken,
teleport.RoleSignup,
}
var HangoutRoles = []teleport.Role{
teleport.RoleAdmin,
teleport.RoleProvisionToken,
teleport.RoleHangoutRemoteUser,
}
const (
ActionGetSessions = "GetSessions"
ActionGetSession = "GetSession"
@ -205,7 +173,10 @@ const (
ActionGetChunkReader = "GetChunkReader"
ActionUpsertServer = "UpsertServer"
ActionGetServers = "GetServers"
ActionUpsertAuthServer = "UpsertAuthServer"
ActionGetAuthServers = "GetAuthServers"
ActionUpsertProxy = "UpsertProxy"
ActionGetProxies = "GetProxies"
ActionUpsertPassword = "UpsertPassword"
ActionCheckPassword = "CheckPassword"
ActionSignIn = "SignIn"

View file

@ -20,42 +20,50 @@ import (
"io/ioutil"
"strings"
"github.com/gravitational/teleport"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/trace"
)
// LocalRegister is used in standalone mode to register roles without
// connecting to remote clients and provisioning tokens
func LocalRegister(dataDir string, id IdentityID, authServer *AuthServer) error {
keys, err := authServer.GenerateServerKeys(id.HostUUID, id.Role)
if err != nil {
return trace.Wrap(err)
}
return writeKeys(dataDir, id, keys.Key, keys.Cert)
}
// Register is used by auth service clients (other services, like proxy or SSH) when a new node
// joins the cluster
func Register(hostUUID, dataDir, token string, role teleport.Role, servers []utils.NetAddr) error {
func Register(dataDir, token string, id IdentityID, servers []utils.NetAddr) error {
tok, err := readToken(token)
if err != nil {
return trace.Wrap(err)
}
method, err := NewTokenAuth(hostUUID, tok)
method, err := NewTokenAuth(id.HostUUID, tok)
if err != nil {
return trace.Wrap(err)
}
client, err := NewTunClient(
servers[0],
hostUUID,
id.HostUUID,
method)
if err != nil {
return trace.Wrap(err)
}
defer client.Close()
keys, err := client.RegisterUsingToken(tok, hostUUID, role)
keys, err := client.RegisterUsingToken(tok, id.HostUUID, id.Role)
if err != nil {
return trace.Wrap(err)
}
return writeKeys(dataDir, keys.Key, keys.Cert)
return writeKeys(dataDir, id, keys.Key, keys.Cert)
}
func RegisterNewAuth(domainName, token string, servers []utils.NetAddr) error {
tok, err := readToken(token)
if err != nil {
return trace.Wrap(err)

View file

@ -316,22 +316,6 @@ func (s *AuthTunnel) keyAuth(
return nil, trace.Errorf("ERROR: Server doesn't support provided key type")
}
if cert.CertType == ssh.UserCert {
_, err := s.userCertChecker.Authenticate(conn, key)
if err != nil {
log.Warningf("conn(%v->%v, user=%v) ERROR: Failed to authorize user %v, err: %v",
conn.RemoteAddr(), conn.LocalAddr(), conn.User(), conn.User(), err)
return nil, err
}
perms := &ssh.Permissions{
Extensions: map[string]string{
ExtHost: conn.User(),
ExtRole: string(teleport.RoleHangoutRemoteUser),
},
}
return perms, nil
}
err := s.hostCertChecker.CheckHostKey(conn.User(), conn.RemoteAddr(), key)
if err != nil {
log.Warningf("conn(%v->%v, user=%v) ERROR: failed auth user %v, err: %v",
@ -584,7 +568,7 @@ func (t *TunDialer) getClient() (*ssh.Client, error) {
return nil, teleport.AccessDenied(
fmt.Sprintf("access denied to '%v': bad username or credentials", t.user))
}
return nil, trace.Wrap(err)
return nil, trace.Wrap(teleport.ConvertSystemError(err))
}
return client, nil
}

View file

@ -79,10 +79,13 @@ func (s *TunSuite) SetUpTest(c *C) {
Authority: authority.New(),
DomainName: "localhost",
})
s.srv = NewAPIWithRoles(s.a, s.bl, sessionServer, s.rec,
NewStandardPermissions(),
StandardRoles,
)
s.srv = NewAPIWithRoles(APIConfig{
AuthServer: s.a,
EventLog: s.bl,
SessionService: sessionServer,
Recorder: s.rec,
PermissionChecker: NewStandardPermissions(),
Roles: StandardRoles})
go s.srv.Serve()
// set up host private key and certificate
@ -111,10 +114,13 @@ func (s *TunSuite) SetUpTest(c *C) {
func (s *TunSuite) TestUnixServerClient(c *C) {
sessionServer, err := session.New(s.bk)
c.Assert(err, IsNil)
srv := NewAPIWithRoles(s.a, s.bl, sessionServer, s.rec,
NewAllowAllPermissions(),
StandardRoles,
)
srv := NewAPIWithRoles(APIConfig{
AuthServer: s.a,
EventLog: s.bl,
SessionService: sessionServer,
Recorder: s.rec,
PermissionChecker: NewAllowAllPermissions(),
Roles: StandardRoles})
go srv.Serve()
tsrv, err := NewTunnel(
@ -145,7 +151,7 @@ func (s *TunSuite) TestUnixServerClient(c *C) {
"test", authMethod)
c.Assert(err, IsNil)
err = clt.UpsertServer(
err = clt.UpsertNode(
services.Server{ID: "a.example.com", Addr: "hello", Hostname: "hello"}, 0)
c.Assert(err, IsNil)
}
@ -343,7 +349,7 @@ func (s *TunSuite) TestPermissions(c *C) {
c.Assert(ws, Not(Equals), "")
// Requesting forbidded for User action
_, err = clt.GetServers()
_, err = clt.GetNodes()
c.Assert(err, NotNil)
// Requesting forbidded for User action
@ -360,7 +366,7 @@ func (s *TunSuite) TestPermissions(c *C) {
defer cltw.Close()
// Requesting forbidden for Web action
_, err = cltw.GetServers()
_, err = cltw.GetNodes()
c.Assert(err, NotNil)
// Requesting forbidden for Web action

View file

@ -147,6 +147,7 @@ func (s *ConfigTestSuite) TestConfigReading(c *check.C) {
c.Assert(conf.Proxy.Configured(), check.Equals, false) // Missing "proxy_service" section must lead to 'not configured'
c.Assert(conf.Proxy.Enabled(), check.Equals, true) // Missing "proxy_service" section must lead to 'true'
c.Assert(conf.Proxy.Disabled(), check.Equals, false) // Missing "proxy_service" does NOT mean it's been disabled
c.Assert(conf.AdvertiseIP.String(), check.Equals, "10.10.10.1")
c.Assert(conf.Limits.MaxConnections, check.Equals, int64(90))
c.Assert(conf.Limits.MaxUsers, check.Equals, 91)
@ -167,7 +168,7 @@ func (s *ConfigTestSuite) TestConfigReading(c *check.C) {
c.Assert(conf.SSH.Commands[1].Name, check.Equals, "date")
c.Assert(conf.SSH.Commands[1].Command, check.DeepEquals, []string{"/bin/date"})
c.Assert(conf.SSH.Commands[1].Period.Nanoseconds(), check.Equals, int64(20000000))
c.Assert(conf.SSH.AdvertiseIP.String(), check.Equals, "10.10.10.1")
}
var (
@ -246,6 +247,7 @@ const (
#
teleport:
nodename: edsger.example.com
advertise_ip: 10.10.10.1
auth_servers:
- tcp://auth0.server.example.org:3024
- tcp://auth1.server.example.org:3024
@ -274,7 +276,6 @@ auth_service:
ssh_service:
enabled: no
listen_addr: tcp://ssh
advertise_ip: 10.10.10.1
labels:
name: mondoserver
role: slave

View file

@ -41,6 +41,7 @@ var (
"auth_service": true,
"auth_token": true,
"auth_servers": true,
"domain_name": true,
"storage": true,
"nodename": true,
"log": true,
@ -190,6 +191,46 @@ func MakeSampleFileConfig() (fc *FileConfig) {
return fc
}
// MakeAuthPeerFileConfig returns a sample configuration for auth
// server peer that shares etcd backend
func MakeAuthPeerFileConfig(domainName string, token string) (fc *FileConfig) {
conf := service.MakeDefaultConfig()
// sample global config:
var g Global
g.NodeName = conf.Hostname
g.AuthToken = token
g.Logger.Output = "stderr"
g.Logger.Severity = "INFO"
g.AuthServers = []string{"<insert auth server peer address here>"}
g.Limits.MaxConnections = defaults.LimiterMaxConnections
g.Limits.MaxUsers = defaults.LimiterMaxConcurrentUsers
g.Storage.DirName = defaults.DataDir
g.Storage.Type = teleport.ETCDBackendType
g.Storage.Prefix = defaults.ETCDPrefix
g.Storage.Peers = []string{"insert ETCD peers addresses here"}
// sample Auth config:
var a Auth
a.ListenAddress = conf.Auth.SSHAddr.Addr
a.EnabledFlag = "yes"
a.DomainName = domainName
var p Proxy
p.EnabledFlag = "no"
var s SSH
s.EnabledFlag = "no"
fc = &FileConfig{
Global: g,
Auth: a,
Proxy: p,
SSH: s,
}
return fc
}
// DebugDumpToYAML allows for quick YAML dumping of the config
func (conf *FileConfig) DebugDumpToYAML() string {
bytes, err := yaml.Marshal(&conf)
@ -239,6 +280,7 @@ type Global struct {
Limits ConnectionLimits `yaml:"connection_limits,omitempty"`
Logger Log `yaml:"log,omitempty"`
Storage StorageBackend `yaml:"storage,omitempty"`
AdvertiseIP net.IP `yaml:"advertise_ip,omitempty"`
}
// Service is a common configuration of a teleport service
@ -266,27 +308,29 @@ func (s *Service) Disabled() bool {
return s.Configured() && !s.Enabled()
}
// 'auth_service' section of the config file
// Auth is 'auth_service' section of the config file
type Auth struct {
Service `yaml:",inline"`
// DomainName is the name of the certificate authority
// managed by this domain
DomainName string `yaml:"domain_name,omitempty"`
}
// 'ssh_service' section of the config file
// SSH is 'ssh_service' section of the config file
type SSH struct {
Service `yaml:",inline"`
Labels map[string]string `yaml:"labels,omitempty"`
Commands []CommandLabel `yaml:"commands,omitempty"`
AdvertiseIP net.IP `yaml:"advertise_ip,omitempty"`
Service `yaml:",inline"`
Labels map[string]string `yaml:"labels,omitempty"`
Commands []CommandLabel `yaml:"commands,omitempty"`
}
// `command` section of `ssh_service` in the config file
// CommandLabel is `command` section of `ssh_service` in the config file
type CommandLabel struct {
Name string `yaml:"name"`
Command []string `yaml:"command,flow"`
Period time.Duration `yaml:"period"`
}
// `proxy_service` section of the config file:
// Proxy is `proxy_service` section of the config file:
type Proxy struct {
Service `yaml:",inline"`
WebAddr string `yaml:"web_listen_addr,omitempty"`

View file

@ -115,6 +115,9 @@ var (
// StartRoles is default roles teleport assumes when started via 'start' command
StartRoles = []string{RoleProxy, RoleNode, RoleAuthService}
// ETCDPrefix is default key in ETCD clustered configurations
ETCDPrefix = "/teleport"
)
const (

View file

@ -756,7 +756,7 @@ func (s *tunnelSite) DialServer(addr string) (net.Conn, error) {
}
var knownServers []services.Server
for i := 0; i < 10; i++ {
knownServers, err = clt.GetServers()
knownServers, err = clt.GetNodes()
if err != nil {
log.Infof("failed to get servers: %v", err)
time.Sleep(time.Second)

View file

@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package service
import (
@ -44,6 +45,10 @@ type Config struct {
AuthServers NetAddrSlice
// AdvertiseIP is used to "publish" an alternative IP address this node
// can be reached on, if running behind NAT
AdvertiseIP net.IP
// SSH role an SSH endpoint server
SSH SSHConfig
@ -184,6 +189,12 @@ type AuthConfig struct {
// TrustedAuthorities is a set of trusted user certificate authorities
TrustedAuthorities CertificateAuthorities
// DomainName is a name that identifies this authority and all
// host nodes in the cluster that will share this authority domain name
// as a base name, e.g. if authority domain name is example.com,
// all nodes in the cluster will have UUIDs in the form: <uuid>.example.com
DomainName string
// UserCA allows to pass preconfigured user certificate authority keypair
// to auth server so it will use it on the first start instead of generating
// a new keypair
@ -225,16 +236,13 @@ type AuthConfig struct {
// SSHConfig configures SSH server node role
type SSHConfig struct {
Enabled bool
Token string
Addr utils.NetAddr
// AdvertiseIP is used to "publish" an alternative IP address this node
// can be reached on, if running behind NAT
AdvertiseIP net.IP
Shell string
Limiter limiter.LimiterConfig
Labels map[string]string
CmdLabels services.CommandLabels
Enabled bool
Token string
Addr utils.NetAddr
Shell string
Limiter limiter.LimiterConfig
Labels map[string]string
CmdLabels services.CommandLabels
}
// ReverseTunnelConfig configures reverse tunnel role

View file

@ -20,8 +20,10 @@ package service
import (
"crypto/tls"
"fmt"
"io"
"io/ioutil"
"net"
"os"
"path/filepath"
"sync"
@ -40,6 +42,7 @@ import (
"github.com/gravitational/teleport/lib/recorder"
"github.com/gravitational/teleport/lib/recorder/boltrec"
"github.com/gravitational/teleport/lib/reversetunnel"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/session"
"github.com/gravitational/teleport/lib/srv"
"github.com/gravitational/teleport/lib/utils"
@ -60,58 +63,50 @@ type RoleConfig struct {
Console io.Writer
}
// connector has all resources process needs to connect
// to other parts of the cluster: client and identity
type connector struct {
identity *auth.Identity
client *auth.TunClient
}
// TeleportProcess structure holds the state of the Teleport daemon, controlling
// execution and configuration of the teleport services: ssh, auth and proxy.
type TeleportProcess struct {
sync.Mutex
Supervisor
Config *Config
Identity *auth.Identity
AuthClient *auth.TunClient
Config *Config
// localAuth has local auth server listed in case if this process
// has started with auth server role enabled
localAuth *auth.AuthServer
}
// loginIntoAuthService attempts to login into the auth servers specified in the
// configuration. Returns 'true' if successful
func (process *TeleportProcess) loginIntoAuthService() bool {
process.Lock()
defer process.Unlock()
// already logged in?
if process.AuthClient != nil {
return true
func (process *TeleportProcess) connectToAuthService(role teleport.Role) (*connector, error) {
identity, err := auth.ReadIdentity(
process.Config.DataDir, auth.IdentityID{HostUUID: process.Config.HostUUID, Role: role})
if err != nil {
return nil, trace.Wrap(err)
}
identity, err := auth.ReadIdentity(process.Config.DataDir)
if identity != nil && err == nil {
authUser := identity.Cert.ValidPrincipals[0]
// try all auth servers in the list:
for _, authServerAddr := range process.Config.AuthServers {
authClient, err := auth.NewTunClient(
authServerAddr,
authUser,
[]ssh.AuthMethod{ssh.PublicKeys(identity.KeySigner)})
// success?
if err != nil {
log.Warning(err)
continue
}
// try calling a test method via auth api:
_, err = authClient.GetLocalDomain()
if err != nil {
log.Warning(err)
continue
}
// success ? we're logged in!
log.Infof("%s logged into %v", authUser, authServerAddr)
process.AuthClient = authClient
process.Identity = identity
return true
}
} else {
log.Warn("no host identity has been found")
authUser := identity.Cert.ValidPrincipals[0]
authClient, err := auth.NewTunClient(
process.Config.AuthServers[0],
authUser,
[]ssh.AuthMethod{ssh.PublicKeys(identity.KeySigner)})
// success?
if err != nil {
return nil, trace.Wrap(err)
}
return false
// try calling a test method via auth api:
_, err = authClient.GetLocalDomain()
if err != nil {
return nil, trace.Wrap(err)
}
// success ? we're logged in!
log.Infof("%s connected to the cluster", authUser)
return &connector{client: authClient, identity: identity}, nil
}
// NewTeleport takes the daemon configuration, instantiates all required services
@ -142,22 +137,24 @@ func NewTeleport(cfg *Config) (Supervisor, error) {
cfg.AuthServers = []utils.NetAddr{cfg.Auth.SSHAddr}
}
// if user did not provide auth domain name, use this host UUID
if cfg.Auth.Enabled && cfg.Auth.DomainName == "" {
cfg.Auth.DomainName = cfg.HostUUID
}
// try to login into the auth service:
// if there are no certificates, use self signed
process := TeleportProcess{
process := &TeleportProcess{
Supervisor: NewSupervisor(),
Config: cfg,
Identity: nil,
AuthClient: nil,
}
process.loginIntoAuthService()
serviceStarted := false
if cfg.Auth.Enabled {
if err := process.InitAuthService(); err != nil {
return nil, err
if err := process.initAuthService(); err != nil {
return nil, trace.Wrap(err)
}
serviceStarted = true
}
@ -190,8 +187,20 @@ func NewTeleport(cfg *Config) (Supervisor, error) {
return process, nil
}
// InitAuthService can be called to initialize auth server service
func (process *TeleportProcess) InitAuthService() error {
func (process *TeleportProcess) setLocalAuth(a *auth.AuthServer) {
process.Lock()
defer process.Unlock()
process.localAuth = a
}
func (process *TeleportProcess) getLocalAuth() *auth.AuthServer {
process.Lock()
defer process.Unlock()
return process.localAuth
}
// initAuthService can be called to initialize auth server service
func (process *TeleportProcess) initAuthService() error {
cfg := process.Config
// Initialize the storage back-ends for keys, events and records
b, err := process.initAuthStorage()
@ -208,30 +217,39 @@ func (process *TeleportProcess) InitAuthService() error {
if err != nil {
return trace.Wrap(err)
}
// configure the auth service:
acfg := auth.InitConfig{
Backend: b,
Authority: authority.New(),
DomainName: cfg.HostUUID,
DomainName: cfg.Auth.DomainName,
AuthServiceName: cfg.Hostname,
DataDir: cfg.DataDir,
SecretKey: cfg.Auth.SecretKey,
AllowedTokens: cfg.Auth.AllowedTokens,
HostUUID: cfg.HostUUID,
}
asrv, signer, err := auth.Init(acfg)
authServer, identity, err := auth.Init(acfg)
if err != nil {
return trace.Wrap(err)
}
sessionService, err := session.New(b)
if err != nil {
return trace.Wrap(err)
}
// set local auth to use from the same process (to simplify setup
// if there are some other roles started in the same process)
process.setLocalAuth(authServer)
sess, err := session.New(b)
if err != nil {
return trace.Wrap(err)
}
apisrv := auth.NewAPIWithRoles(asrv, elog, sess, rec,
auth.NewStandardPermissions(), auth.StandardRoles,
)
apiServer := auth.NewAPIWithRoles(auth.APIConfig{
AuthServer: authServer,
EventLog: elog,
SessionService: sessionService,
Recorder: rec,
PermissionChecker: auth.NewStandardPermissions(),
Roles: auth.StandardRoles,
})
process.RegisterFunc(func() error {
apisrv.Serve()
apiServer.Serve()
return nil
})
@ -245,9 +263,9 @@ func (process *TeleportProcess) InitAuthService() error {
process.RegisterFunc(func() error {
utils.Consolef(cfg.Console, "[AUTH] Auth service is starting on %v", cfg.Auth.SSHAddr.Addr)
tsrv, err := auth.NewTunnel(
cfg.Auth.SSHAddr, []ssh.Signer{signer},
apisrv,
asrv,
cfg.Auth.SSHAddr, []ssh.Signer{identity.KeySigner},
apiServer,
authServer,
auth.SetLimiter(limiter),
)
if err != nil {
@ -260,15 +278,50 @@ func (process *TeleportProcess) InitAuthService() error {
}
return nil
})
// Heart beat auth server presence, this is not the best place for this
// logic, consolidate it into auth package later
process.RegisterFunc(func() error {
authClient, err := auth.NewTunClient(
cfg.Auth.SSHAddr,
identity.Cert.ValidPrincipals[0],
[]ssh.AuthMethod{ssh.PublicKeys(identity.KeySigner)})
// success?
if err != nil {
return trace.Wrap(err)
}
srv := services.Server{
ID: process.Config.HostUUID,
Addr: cfg.Auth.SSHAddr.Addr,
Hostname: process.Config.Hostname,
}
if process.Config.AdvertiseIP != nil {
_, port, err := net.SplitHostPort(srv.Addr)
if err != nil {
return trace.Wrap(err)
}
srv.Addr = fmt.Sprintf("%v:%v", process.Config.AdvertiseIP.String(), port)
}
for {
err := authClient.UpsertAuthServer(srv, defaults.ServerHeartbeatTTL)
if err != nil {
log.Warningf("failed to announce presence: %v", err)
}
sleepTime := defaults.ServerHeartbeatTTL/2 + utils.RandomDuration(defaults.ServerHeartbeatTTL/10)
log.Infof("[AUTH] will ping auth service in %v", sleepTime)
time.Sleep(sleepTime)
}
})
return nil
}
func (process *TeleportProcess) initSSH() error {
return process.RegisterWithAuthServer(process.Config.SSH.Token, teleport.RoleNode,
func() error { return process.initSSHEndpoint() })
return process.RegisterWithAuthServer(
process.Config.SSH.Token, teleport.RoleNode,
process.initSSHEndpoint)
}
func (process *TeleportProcess) initSSHEndpoint() error {
func (process *TeleportProcess) initSSHEndpoint(conn *connector) error {
cfg := process.Config
limiter, err := limiter.NewLimiter(cfg.SSH.Limiter)
@ -278,15 +331,15 @@ func (process *TeleportProcess) initSSHEndpoint() error {
s, err := srv.New(cfg.SSH.Addr,
cfg.Hostname,
[]ssh.Signer{process.Identity.KeySigner},
process.AuthClient,
[]ssh.Signer{conn.identity.KeySigner},
conn.client,
cfg.DataDir,
cfg.SSH.AdvertiseIP,
cfg.AdvertiseIP,
srv.SetLimiter(limiter),
srv.SetShell(cfg.SSH.Shell),
srv.SetEventLogger(process.AuthClient),
srv.SetSessionServer(process.AuthClient),
srv.SetRecorder(process.AuthClient),
srv.SetEventLogger(conn.client),
srv.SetSessionServer(conn.client),
srv.SetRecorder(conn.client),
srv.SetLabels(cfg.SSH.Labels, cfg.SSH.CmdLabels),
)
if err != nil {
@ -308,62 +361,69 @@ func (process *TeleportProcess) initSSHEndpoint() error {
// RegisterWithAuthServer uses one time provisioning token obtained earlier
// from the server to get a pair of SSH keys signed by Auth server host
// certificate authority
func (process *TeleportProcess) RegisterWithAuthServer(token string, role teleport.Role, callback func() error) error {
func (process *TeleportProcess) RegisterWithAuthServer(token string, role teleport.Role, callback func(conn *connector) error) error {
cfg := process.Config
authServer := cfg.AuthServers[0].Addr
identityID := auth.IdentityID{Role: role, HostUUID: cfg.HostUUID}
// this means the server has not been initialized yet, we are starting
// the registering client that attempts to connect to the auth server
// and provision the keys
process.RegisterFunc(func() error {
loggedIn := false
for !loggedIn {
if token != "" {
log.Infof("joining the cluster with a token %v", token)
err := auth.Register(cfg.HostUUID, cfg.DataDir, token, role, cfg.AuthServers)
if err != nil {
log.Errorf("[SSH] failed to join the cluster: %v", err)
}
for {
conn, err := process.connectToAuthService(role)
if err == nil {
return callback(conn)
}
loggedIn = process.loginIntoAuthService()
if !loggedIn {
time.Sleep(time.Second * 5)
if teleport.IsConnectionProblem(err) {
log.Errorf("[%v] failed connect to auth serverr: %v", role, err)
time.Sleep(time.Second)
continue
}
if !teleport.IsNotFound(err) {
return trace.Wrap(err)
}
// we haven't connected yet, so we expect the token to exist
if process.getLocalAuth() != nil {
// Auth service is on the same host, no need to go though the invitation
// procedure
log.Infof("this server has local Auth server started, using it to add role to the cluster")
err = auth.LocalRegister(cfg.DataDir, identityID, process.getLocalAuth())
} else {
// Auth server is remote, so we need a provisioning token
if token == "" {
return trace.Wrap(teleport.BadParameter(role.String(), "role has no identity and no provisioning token"))
}
log.Infof("%v joining the cluster with a token %v", role, token)
err = auth.Register(cfg.DataDir, token, identityID, cfg.AuthServers)
}
if err != nil {
log.Errorf("[%v] failed to join the cluster: %v", role, err)
time.Sleep(time.Second)
} else {
utils.Consolef(os.Stdout, "[%v] Successfully registered with the cluster", role)
continue
}
}
utils.Consolef(os.Stdout, "[SSH] Successfully registered with the auth server %v", authServer)
return callback()
})
return nil
}
func (process *TeleportProcess) initReverseTunnel() error {
return process.RegisterWithAuthServer(process.Config.Proxy.Token, teleport.RoleNode,
func() error { return process.initTunAgent() })
return process.RegisterWithAuthServer(
process.Config.Proxy.Token,
teleport.RoleNode,
process.initTunAgent)
}
func (process *TeleportProcess) initTunAgent() error {
func (process *TeleportProcess) initTunAgent(conn *connector) error {
cfg := process.Config
i, err := auth.ReadIdentity(cfg.DataDir)
if err != nil {
return trace.Wrap(err)
}
endpointUser := i.Cert.ValidPrincipals[0]
client, err := auth.NewTunClient(
cfg.AuthServers[0],
endpointUser,
[]ssh.AuthMethod{ssh.PublicKeys(i.KeySigner)})
if err != nil {
return trace.Wrap(err)
}
a, err := reversetunnel.NewAgent(
cfg.ReverseTunnel.DialAddr,
cfg.Hostname,
[]ssh.Signer{i.KeySigner},
client,
reversetunnel.SetEventLogger(client))
[]ssh.Signer{conn.identity.KeySigner},
conn.client,
reversetunnel.SetEventLogger(conn.client))
if err != nil {
return trace.Wrap(err)
}
@ -392,11 +452,12 @@ func (process *TeleportProcess) initProxy() (err error) {
return trace.Wrap(err)
}
}
return process.RegisterWithAuthServer(process.Config.Proxy.Token, teleport.RoleNode,
func() error { return process.initProxyEndpoint() })
return process.RegisterWithAuthServer(
process.Config.Proxy.Token, teleport.RoleProxy,
process.initProxyEndpoint)
}
func (process *TeleportProcess) initProxyEndpoint() error {
func (process *TeleportProcess) initProxyEndpoint(conn *connector) error {
cfg := process.Config
proxyLimiter, err := limiter.NewLimiter(cfg.Proxy.Limiter)
if err != nil {
@ -408,26 +469,12 @@ func (process *TeleportProcess) initProxyEndpoint() error {
return trace.Wrap(err)
}
i, err := auth.ReadIdentity(cfg.DataDir)
if err != nil {
return trace.Wrap(err)
}
endpointUser := i.Cert.ValidPrincipals[0]
client, err := auth.NewTunClient(
cfg.AuthServers[0],
endpointUser,
[]ssh.AuthMethod{ssh.PublicKeys(i.KeySigner)})
if err != nil {
return trace.Wrap(err)
}
tsrv, err := reversetunnel.NewServer(
cfg.Proxy.ReverseTunnelListenAddr,
[]ssh.Signer{i.KeySigner},
client,
[]ssh.Signer{conn.identity.KeySigner},
conn.client,
reversetunnel.SetLimiter(reverseTunnelLimiter),
reversetunnel.DirectSite(i.Cert.Extensions[utils.CertExtensionAuthority], client),
reversetunnel.DirectSite(conn.identity.Cert.Extensions[utils.CertExtensionAuthority], conn.client),
)
if err != nil {
return trace.Wrap(err)
@ -435,13 +482,13 @@ func (process *TeleportProcess) initProxyEndpoint() error {
SSHProxy, err := srv.New(cfg.Proxy.SSHAddr,
cfg.Hostname,
[]ssh.Signer{i.KeySigner},
client,
[]ssh.Signer{conn.identity.KeySigner},
conn.client,
cfg.DataDir,
nil,
srv.SetLimiter(proxyLimiter),
srv.SetProxyMode(tsrv),
srv.SetSessionServer(client),
srv.SetSessionServer(conn.client),
)
if err != nil {
return trace.Wrap(err)

View file

@ -19,32 +19,33 @@ package services
import (
"encoding/json"
"fmt"
"sort"
"strings"
"time"
"github.com/gravitational/teleport/lib/backend"
"github.com/gravitational/trace"
log "github.com/Sirupsen/logrus"
)
// PresenceService records and reports the presence of all components
// of the cluster - Nodes, Proxies and SSH nodes
type PresenceService struct {
backend backend.Backend
}
// NewPresenceService returns new presence service instance
func NewPresenceService(backend backend.Backend) *PresenceService {
return &PresenceService{backend}
}
// GetServers returns a list of registered servers
func (s *PresenceService) GetServers() ([]Server, error) {
IDs, err := s.backend.GetKeys([]string{"servers"})
func (s *PresenceService) getServers(prefix string) ([]Server, error) {
IDs, err := s.backend.GetKeys([]string{prefix})
if err != nil {
return nil, trace.Wrap(err)
}
servers := make([]Server, len(IDs))
for i, id := range IDs {
data, err := s.backend.GetVal([]string{"servers"}, id)
data, err := s.backend.GetVal([]string{prefix}, id)
if err != nil {
return nil, trace.Wrap(err)
}
@ -52,57 +53,71 @@ func (s *PresenceService) GetServers() ([]Server, error) {
return nil, trace.Wrap(err)
}
}
// sorting helps with tests and makes it all deterministic
sort.Sort(sortedServers(servers))
return servers, nil
}
// UpsertServer registers server presence, permanently if ttl is 0 or
// for the specified duration with second resolution if it's >= 1 second
func (s *PresenceService) UpsertServer(server Server, ttl time.Duration) error {
type sortedServers []Server
func (s sortedServers) Len() int {
return len(s)
}
func (s sortedServers) Less(i, j int) bool {
return s[i].ID < s[j].ID
}
func (s sortedServers) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s *PresenceService) upsertServer(prefix string, server Server, ttl time.Duration) error {
data, err := json.Marshal(server)
if err != nil {
log.Error(err)
return trace.Wrap(err)
}
err = s.backend.UpsertVal([]string{"servers"}, server.ID, data, ttl)
if err != nil {
log.Error(err)
return trace.Wrap(err)
}
return err
err = s.backend.UpsertVal([]string{prefix}, server.ID, data, ttl)
return trace.Wrap(err)
}
// GetServers returns a list of registered servers
const (
nodesPrefix = "nodes"
authServersPrefix = "authservers"
proxiesPrefix = "proxies"
)
// GetNodes returns a list of registered servers
func (s *PresenceService) GetNodes() ([]Server, error) {
return s.getServers(nodesPrefix)
}
// UpsertNode registers node presence, permanently if ttl is 0 or
// for the specified duration with second resolution if it's >= 1 second
func (s *PresenceService) UpsertNode(server Server, ttl time.Duration) error {
return s.upsertServer(nodesPrefix, server, ttl)
}
// GetAuthServers returns a list of registered servers
func (s *PresenceService) GetAuthServers() ([]Server, error) {
IDs, err := s.backend.GetKeys([]string{"authservers"})
if err != nil {
return nil, trace.Wrap(err)
}
servers := make([]Server, len(IDs))
for i, id := range IDs {
data, err := s.backend.GetVal([]string{"authservers"}, id)
if err != nil {
return nil, trace.Wrap(err)
}
if err := json.Unmarshal(data, &servers[i]); err != nil {
return nil, trace.Wrap(err)
}
}
return servers, nil
return s.getServers(authServersPrefix)
}
// UpsertServer registers server presence, permanently if ttl is 0 or
// UpsertAuthServer registers auth server presence, permanently if ttl is 0 or
// for the specified duration with second resolution if it's >= 1 second
func (s *PresenceService) UpsertAuthServer(server Server, ttl time.Duration) error {
data, err := json.Marshal(server)
if err != nil {
return trace.Wrap(err)
}
err = s.backend.UpsertVal([]string{"authservers"},
server.ID, data, ttl)
if err != nil {
return trace.Wrap(err)
}
return err
return s.upsertServer(authServersPrefix, server, ttl)
}
// UpsertProxy registers proxy server presence, permanently if ttl is 0 or
// for the specified duration with second resolution if it's >= 1 second
func (s *PresenceService) UpsertProxy(server Server, ttl time.Duration) error {
return s.upsertServer(proxiesPrefix, server, ttl)
}
// GetProxies returns a list of registered proxies
func (s *PresenceService) GetProxies() ([]Server, error) {
return s.getServers(proxiesPrefix)
}
type Server struct {

View file

@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package services
import (

View file

@ -156,16 +156,38 @@ func (s *ServicesTestSuite) CertAuthCRUD(c *C) {
}
func (s *ServicesTestSuite) ServerCRUD(c *C) {
out, err := s.PresenceS.GetServers()
out, err := s.PresenceS.GetNodes()
c.Assert(err, IsNil)
c.Assert(len(out), Equals, 0)
srv := Server{ID: "srv1", Addr: "localhost:2022"}
c.Assert(s.PresenceS.UpsertServer(srv, 0), IsNil)
c.Assert(s.PresenceS.UpsertNode(srv, 0), IsNil)
out, err = s.PresenceS.GetServers()
out, err = s.PresenceS.GetNodes()
c.Assert(err, IsNil)
c.Assert(out, DeepEquals, []Server{srv})
out, err = s.PresenceS.GetProxies()
c.Assert(err, IsNil)
c.Assert(len(out), Equals, 0)
proxy := Server{ID: "proxy1", Addr: "localhost:2023"}
c.Assert(s.PresenceS.UpsertProxy(proxy, 0), IsNil)
out, err = s.PresenceS.GetProxies()
c.Assert(err, IsNil)
c.Assert(out, DeepEquals, []Server{proxy})
out, err = s.PresenceS.GetAuthServers()
c.Assert(err, IsNil)
c.Assert(len(out), Equals, 0)
auth := Server{ID: "auth1", Addr: "localhost:2025"}
c.Assert(s.PresenceS.UpsertAuthServer(auth, 0), IsNil)
out, err = s.PresenceS.GetAuthServers()
c.Assert(err, IsNil)
c.Assert(out, DeepEquals, []Server{auth})
}
func (s *ServicesTestSuite) PasswordHashCRUD(c *C) {

View file

@ -77,7 +77,7 @@ func (t *proxySubsys) start(sconn *ssh.ServerConn, ch ssh.Channel, req *ssh.Requ
if err != nil {
return trace.Wrap(err)
}
servers, err := clt.GetServers()
servers, err := clt.GetNodes()
if err != nil {
return trace.Wrap(err)
}

View file

@ -39,7 +39,7 @@ func (b *backendResolver) resolve(query string) ([]string, error) {
// simply expand the query to all known hosts
if query == "*" {
out := []string{}
srvs, err := b.authService.GetServers()
srvs, err := b.authService.GetNodes()
if err != nil {
return nil, err
}

View file

@ -37,7 +37,7 @@ func (t *proxySitesSubsys) start(sconn *ssh.ServerConn, ch ssh.Channel, req *ssh
if err != nil {
return trace.Wrap(err)
}
servers, err := clt.GetServers()
servers, err := clt.GetNodes()
if err != nil {
return trace.Wrap(err)
}

View file

@ -264,7 +264,10 @@ func (s *Server) registerServer() error {
Labels: s.labels,
CmdLabels: s.getCommandLabels(),
}
return trace.Wrap(s.authService.UpsertServer(srv, defaults.ServerHeartbeatTTL))
if !s.proxyMode {
return trace.Wrap(s.authService.UpsertNode(srv, defaults.ServerHeartbeatTTL))
}
return trace.Wrap(s.authService.UpsertProxy(srv, defaults.ServerHeartbeatTTL))
}
// heartbeatPresence periodically calls into the auth server to let everyone
@ -482,12 +485,10 @@ func (s *Server) Close() error {
// Start starts server
func (s *Server) Start() error {
if !s.proxyMode {
if len(s.cmdLabels) > 0 {
s.updateLabels()
}
go s.heartbeatPresence()
if len(s.cmdLabels) > 0 {
s.updateLabels()
}
go s.heartbeatPresence()
return s.srv.Start()
}

View file

@ -340,10 +340,13 @@ func (s *SrvSuite) TestProxyReverseTunnel(c *C) {
sessionServer, err := sess.New(s.bk)
c.Assert(err, IsNil)
apiSrv := auth.NewAPIWithRoles(s.a, bl, sessionServer, rec,
auth.NewAllowAllPermissions(),
auth.StandardRoles,
)
apiSrv := auth.NewAPIWithRoles(auth.APIConfig{
AuthServer: s.a,
EventLog: bl,
SessionService: sessionServer,
Recorder: rec,
PermissionChecker: auth.NewAllowAllPermissions(),
Roles: auth.StandardRoles})
go apiSrv.Serve()
tsrv, err := auth.NewTunnel(
@ -504,10 +507,13 @@ func (s *SrvSuite) TestProxyRoundRobin(c *C) {
sessionServer, err := sess.New(s.bk)
c.Assert(err, IsNil)
apiSrv := auth.NewAPIWithRoles(s.a, bl, sessionServer, rec,
auth.NewAllowAllPermissions(),
auth.StandardRoles,
)
apiSrv := auth.NewAPIWithRoles(auth.APIConfig{
AuthServer: s.a,
EventLog: bl,
SessionService: sessionServer,
Recorder: rec,
PermissionChecker: auth.NewAllowAllPermissions(),
Roles: auth.StandardRoles})
go apiSrv.Serve()
tsrv, err := auth.NewTunnel(
@ -602,10 +608,13 @@ func (s *SrvSuite) TestProxyDirectAccess(c *C) {
sessionServer, err := sess.New(s.bk)
c.Assert(err, IsNil)
apiSrv := auth.NewAPIWithRoles(s.a, bl, sessionServer, rec,
auth.NewAllowAllPermissions(),
auth.StandardRoles,
)
apiSrv := auth.NewAPIWithRoles(auth.APIConfig{
AuthServer: s.a,
EventLog: bl,
SessionService: sessionServer,
Recorder: rec,
PermissionChecker: auth.NewAllowAllPermissions(),
Roles: auth.StandardRoles})
go apiSrv.Serve()
tsrv, err := auth.NewTunnel(

View file

@ -17,9 +17,6 @@ limitations under the License.
package utils
import (
"archive/tar"
"fmt"
log "github.com/Sirupsen/logrus"
"io"
"io/ioutil"
"net"
@ -28,6 +25,9 @@ import (
"strconv"
"strings"
"github.com/gravitational/teleport"
log "github.com/Sirupsen/logrus"
"github.com/gravitational/trace"
"github.com/pborman/uuid"
"golang.org/x/crypto/ssh"
@ -38,56 +38,19 @@ type HostKeyCallback func(hostID string, remote net.Addr, key ssh.PublicKey) err
func ReadPath(path string) ([]byte, error) {
s, err := filepath.Abs(path)
if err != nil {
return nil, fmt.Errorf("failed to convert path %v, err %v", s, err)
return nil, trace.Wrap(teleport.ConvertSystemError(err))
}
abs, err := filepath.EvalSymlinks(s)
if err != nil {
return nil, fmt.Errorf("failed to eval symlinks in path %v, err %v", path, err)
return nil, trace.Wrap(teleport.ConvertSystemError(err))
}
bytes, err := ioutil.ReadFile(abs)
if err != nil {
return nil, err
return nil, trace.Wrap(teleport.ConvertSystemError(err))
}
return bytes, nil
}
func WriteArchive(root_directory string, w io.Writer) error {
ar := tar.NewWriter(w)
walkFn := func(path string, info os.FileInfo, err error) error {
if info.Mode().IsDir() {
return nil
}
// Because of scoping we can reference the external root_directory variable
new_path := path[len(root_directory):]
if len(new_path) == 0 {
return nil
}
fr, err := os.Open(path)
if err != nil {
return err
}
defer fr.Close()
if h, err := tar.FileInfoHeader(info, new_path); err != nil {
return err
} else {
h.Name = new_path
if err = ar.WriteHeader(h); err != nil {
return err
}
}
if length, err := io.Copy(ar, fr); err != nil {
return err
} else {
fmt.Println(length)
}
return nil
}
return filepath.Walk(root_directory, walkFn)
}
type multiCloser struct {
closers []io.Closer
}
@ -101,8 +64,7 @@ func (mc *multiCloser) Close() error {
return nil
}
// MultiCloser implements io.Close,
// it sequentially calls Close() on each object
// MultiCloser implements io.Close, it sequentially calls Close() on each object
func MultiCloser(closers ...io.Closer) *multiCloser {
return &multiCloser{
closers: closers,

View file

@ -50,7 +50,7 @@ func newConnectHandler(req connectReq, ctx *sessionContext, site reversetunnel.R
if err != nil {
return nil, trace.Wrap(err)
}
servers, err := clt.GetServers()
servers, err := clt.GetNodes()
if err != nil {
return nil, trace.Wrap(err)
}

View file

@ -97,7 +97,7 @@ func (w *sessionStreamHandler) stream(ws *websocket.Conn) error {
}
}
servers, err := clt.GetServers()
servers, err := clt.GetNodes()
if err != nil {
if !teleport.IsNotFound(err) {
return trace.Wrap(err)

View file

@ -429,7 +429,7 @@ func (m *Handler) getSiteNodes(w http.ResponseWriter, r *http.Request, _ httprou
if err != nil {
return nil, trace.Wrap(err)
}
servers, err := clt.GetServers()
servers, err := clt.GetNodes()
if err != nil {
return nil, trace.Wrap(err)
}

View file

@ -170,10 +170,13 @@ func (s *WebSuite) SetUpTest(c *C) {
apiPort := s.freePorts[len(s.freePorts)-1]
s.freePorts = s.freePorts[:len(s.freePorts)-1]
apiServer := auth.NewAPIWithRoles(authServer, eventsLog, sessionServer, recorder,
auth.NewAllowAllPermissions(),
auth.StandardRoles,
)
apiServer := auth.NewAPIWithRoles(auth.APIConfig{
AuthServer: authServer,
EventLog: eventsLog,
SessionService: sessionServer,
Recorder: recorder,
PermissionChecker: auth.NewAllowAllPermissions(),
Roles: auth.StandardRoles})
go apiServer.Serve()
tunAddr := utils.NetAddr{

View file

@ -2,6 +2,7 @@ package teleport
import (
"fmt"
"strings"
"github.com/gravitational/trace"
)
@ -9,11 +10,16 @@ import (
// Role identifies the role of SSH server connection
type Role string
// String returns debug-friendly representation of this role
func (r Role) String() string {
return fmt.Sprintf("%v", strings.ToUpper(string(r)))
}
// Check checks if this a a valid role value, returns nil
// if it's ok, false otherwise
func (r Role) Check() error {
switch r {
case RoleAuth, RoleUser, RoleWeb, RoleNode, RoleAdmin, RoleProvisionToken, RoleSignup, RoleHangoutRemoteUser:
case RoleAuth, RoleUser, RoleWeb, RoleNode, RoleAdmin, RoleProvisionToken, RoleSignup, RoleProxy:
return nil
}
return trace.Wrap(BadParameter("role", fmt.Sprintf("%v is not supported", r)))
@ -28,12 +34,12 @@ const (
RoleWeb Role = "Web"
// RoleNode is a role for SSH node in the cluster
RoleNode Role = "Node"
// RoleProxy is a role for SSH proxy in the cluster
RoleProxy Role = "Proxy"
// RoleAdmin is admin role
RoleAdmin Role = "Admin"
// RoleProvisionToken is a role for
// RoleProvisionToken is a role for nodes authenticated using provisioning tokens
RoleProvisionToken Role = "ProvisionToken"
// RoleSignup is for first time signing up users
RoleSignup Role = "Signup"
// RoleHangoutRemoteUser is for users joining remote hangouts
RoleHangoutRemoteUser Role = "HangoutRemoteUser"
)

View file

@ -25,6 +25,7 @@ import (
"github.com/gravitational/teleport"
"github.com/gravitational/teleport/lib/auth"
"github.com/gravitational/teleport/lib/config"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/service"
"github.com/gravitational/teleport/lib/services"
@ -56,6 +57,10 @@ type AuthCommand struct {
authType string
}
type AuthServerCommand struct {
config *service.Config
}
func main() {
utils.InitLoggerCLI()
app := utils.InitCLIParser("tctl", GlobalHelpString)
@ -65,6 +70,7 @@ func main() {
cmdUsers := UserCommand{config: cfg}
cmdNodes := NodeCommand{config: cfg}
cmdAuth := AuthCommand{config: cfg}
cmdAuthServers := AuthServerCommand{config: cfg}
// define global flags:
var ccf CLIConfig
@ -105,6 +111,10 @@ func main() {
authList := auth.Command("ls", "List trusted user certificate authorities").Hidden()
authExport := auth.Command("export", "Export concatenated keys to standard output").Hidden()
// operations with auth servers
authServers := app.Command("authservers", "Operations with user and host certificate authorities").Hidden()
authServerAdd := authServers.Command("add", "Add a new auth server node to the cluster").Hidden()
// parse CLI commands+flags:
command, err := app.Parse(os.Args[1:])
if err != nil {
@ -142,6 +152,8 @@ func main() {
err = cmdAuth.ListAuthorities(client)
case authExport.FullCommand():
err = cmdAuth.ExportAuthorities(client)
case authServerAdd.FullCommand():
err = cmdAuthServers.Invite(client)
}
if err != nil {
@ -233,7 +245,7 @@ func (u *NodeCommand) Invite(client *auth.TunClient) error {
// ListActive retreives the list of nodes who recently sent heartbeats to
// to a cluster and prints it to stdout
func (u *NodeCommand) ListActive(client *auth.TunClient) error {
nodes, err := client.GetServers()
nodes, err := client.GetNodes()
if err != nil {
return trace.Wrap(err)
}
@ -310,6 +322,27 @@ func (a *AuthCommand) ExportAuthorities(client *auth.TunClient) error {
return nil
}
// Invite generates a token which can be used to add another SSH auth server
// to the cluster
func (u *AuthServerCommand) Invite(client *auth.TunClient) error {
authDomainName, err := client.GetLocalDomain()
if err != nil {
return trace.Wrap(err)
}
invitationTTL := time.Minute * 15
token, err := client.GenerateToken(teleport.RoleAuth, invitationTTL)
if err != nil {
return trace.Wrap(err)
}
cfg := config.MakeAuthPeerFileConfig(authDomainName, token)
out := cfg.DebugDumpToYAML()
fmt.Printf(
"# Run this config the new auth server to join the cluster:\n# > teleport start --config config.yaml\n# Fill in auth peers in this config:\n")
fmt.Println(out)
return nil
}
// connectToAuthService creates a valid client connection to the auth service
func connectToAuthService(cfg *service.Config) (client *auth.TunClient, err error) {
// connect to the local auth server by default:
@ -319,7 +352,7 @@ func connectToAuthService(cfg *service.Config) (client *auth.TunClient, err erro
}
// read the host SSH keys and use them to open an SSH connection to the auth service
i, err := auth.ReadIdentity(cfg.DataDir)
i, err := auth.ReadIdentity(cfg.DataDir, auth.IdentityID{Role: teleport.RoleAdmin, HostUUID: cfg.HostUUID})
if err != nil {
return nil, trace.Wrap(err)
}

View file

@ -95,6 +95,15 @@ func applyFileConfig(fc *config.FileConfig, cfg *service.Config) error {
}
applyString(fc.NodeName, &cfg.Hostname)
// apply "advertise_ip" setting:
advertiseIP := fc.AdvertiseIP
if advertiseIP != nil {
if err := validateAdvertiseIP(advertiseIP); err != nil {
return trace.Wrap(err)
}
cfg.AdvertiseIP = advertiseIP
}
// config file has auth servers in there?
if len(fc.AuthServers) > 0 {
cfg.AuthServers = make(service.NetAddrSlice, 0, len(fc.AuthServers))
@ -107,6 +116,7 @@ func applyFileConfig(fc *config.FileConfig, cfg *service.Config) error {
}
}
cfg.ApplyToken(fc.AuthToken)
cfg.Auth.DomainName = fc.Auth.DomainName
// configure storage:
switch fc.Storage.Type {
@ -235,14 +245,6 @@ func applyFileConfig(fc *config.FileConfig, cfg *service.Config) error {
}
}
}
// apply "advertise_ip" setting:
advertiseIP := fc.SSH.AdvertiseIP
if advertiseIP != nil {
if advertiseIP.IsLoopback() || advertiseIP.IsUnspecified() || advertiseIP.IsMulticast() {
return teleport.BadParameter("advertise-ip", fmt.Sprintf("unreachable advertise IP: %v", advertiseIP))
}
cfg.SSH.AdvertiseIP = advertiseIP
}
return nil
}
@ -270,7 +272,6 @@ func configure(clf *CommandLineFlags) (cfg *service.Config, err error) {
if err = applyFileConfig(fileConf, cfg); err != nil {
return nil, trace.Wrap(err)
}
// apply --debug flag:
if clf.Debug {
cfg.Console = ioutil.Discard
@ -315,6 +316,13 @@ func configure(clf *CommandLineFlags) (cfg *service.Config, err error) {
applyListenIP(clf.ListenIP, cfg)
}
if clf.AdvertiseIP != nil {
if err := validateAdvertiseIP(clf.AdvertiseIP); err != nil {
return nil, trace.Wrap(err)
}
cfg.AdvertiseIP = clf.AdvertiseIP
}
return cfg, nil
}
@ -366,3 +374,10 @@ func validateRoles(roles string) error {
}
return nil
}
func validateAdvertiseIP(advertiseIP net.IP) error {
if advertiseIP.IsLoopback() || advertiseIP.IsUnspecified() || advertiseIP.IsMulticast() {
return teleport.BadParameter("advertise-ip", fmt.Sprintf("unreachable advertise IP: %v", advertiseIP))
}
return nil
}

View file

@ -101,11 +101,12 @@ func (s *MainTestSuite) TestConfigFile(c *check.C) {
c.Assert(log.GetLevel(), check.Equals, log.DebugLevel)
c.Assert(conf.Hostname, check.Equals, "hvostongo.example.org")
c.Assert(conf.SSH.Token, check.Equals, "xxxyyy")
c.Assert(conf.SSH.AdvertiseIP, check.DeepEquals, net.ParseIP("10.5.5.5"))
c.Assert(conf.AdvertiseIP, check.DeepEquals, net.ParseIP("10.5.5.5"))
}
const YAMLConfig = `
teleport:
advertise_ip: 10.5.5.5
nodename: hvostongo.example.org
auth_servers:
- tcp://auth.server.example.org:3024
@ -131,7 +132,6 @@ auth_service:
ssh_service:
enabled: no
listen_addr: tcp://ssh
advertise_ip: 10.5.5.5
labels:
name: mondoserver
role: slave