mirror of
https://github.com/gravitational/teleport
synced 2024-10-22 02:03:24 +00:00
draft OIDC support
This commit is contained in:
parent
b0bdd3e248
commit
84cade14c5
37
errors.go
37
errors.go
|
@ -20,6 +20,7 @@ import (
|
|||
"crypto/x509"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
|
@ -488,3 +489,39 @@ func IsTrustError(e error) bool {
|
|||
_, ok := e.(te)
|
||||
return ok
|
||||
}
|
||||
|
||||
// OAuth2Error is error returned during OAuth2 authentication
|
||||
// currently used in OIDC (OpenID Connect flow)
|
||||
type OAuth2Error struct {
|
||||
Code string `code:"code"`
|
||||
Message string `message:"message"`
|
||||
Query url.Values `query:"query"`
|
||||
}
|
||||
|
||||
// NewOAuth2Error returns new instance of OAuth2Error
|
||||
func NewOAuth2Error(code, message string, query url.Values) *OAuth2Error {
|
||||
return &OAuth2Error{
|
||||
Code: code,
|
||||
Message: message,
|
||||
Query: query,
|
||||
}
|
||||
}
|
||||
|
||||
// Error returns debug friendly error message
|
||||
func (o *OAuth2Error) Error() string {
|
||||
return fmt.Sprintf("OAuth2 error code=%v, message=%v", o.Code, o.Message)
|
||||
}
|
||||
|
||||
// IsOAuth2Error indicates that this error of OAuth2 type
|
||||
func (o *OAuth2Error) IsOAuth2Error() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsOAuth2Error returns if this is a OAuth2-related error
|
||||
func IsOAuth2Error(e error) bool {
|
||||
type oe interface {
|
||||
IsOAuth2Error() bool
|
||||
}
|
||||
_, ok := e.(oe)
|
||||
return ok
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ package auth
|
|||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
|
@ -127,6 +128,14 @@ func NewAPIServer(a *AuthWithRoles) *APIServer {
|
|||
srv.GET("/v1/sessions", httplib.MakeHandler(srv.getSessions))
|
||||
srv.GET("/v1/sessions/:id", httplib.MakeHandler(srv.getSession))
|
||||
|
||||
// OIDC stuff
|
||||
srv.POST("/v1/oidc/connectors", httplib.MakeHandler(srv.upsertOIDCConnector))
|
||||
srv.GET("/v1/oidc/connectors", httplib.MakeHandler(srv.getOIDCConnectors))
|
||||
srv.GET("/v1/oidc/connectors/:id", httplib.MakeHandler(srv.getOIDCConnector))
|
||||
srv.DELETE("/v1/oidc/connectors/:id", httplib.MakeHandler(srv.deleteOIDCConnector))
|
||||
srv.POST("/v1/oidc/requests/create", httplib.MakeHandler(srv.createOIDCAuthRequest))
|
||||
srv.POST("/v1/oidc/requests/validate", httplib.MakeHandler(srv.validateOIDCAuthCallback))
|
||||
|
||||
return srv
|
||||
}
|
||||
|
||||
|
@ -759,8 +768,7 @@ func (s *APIServer) getSignupTokenData(w http.ResponseWriter, r *http.Request, p
|
|||
}
|
||||
|
||||
type createSignupTokenReq struct {
|
||||
User string `json:"user"`
|
||||
AllowedLogins []string `json:"allowed_logins"`
|
||||
User services.User `json:"user"`
|
||||
}
|
||||
|
||||
func (s *APIServer) createSignupToken(w http.ResponseWriter, r *http.Request, p httprouter.Params) (interface{}, error) {
|
||||
|
@ -768,7 +776,7 @@ func (s *APIServer) createSignupToken(w http.ResponseWriter, r *http.Request, p
|
|||
if err := httplib.ReadJSON(r, &req); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
token, err := s.a.CreateSignupToken(req.User, req.AllowedLogins)
|
||||
token, err := s.a.CreateSignupToken(req.User)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
@ -793,6 +801,101 @@ func (s *APIServer) createUserWithToken(w http.ResponseWriter, r *http.Request,
|
|||
return sess, nil
|
||||
}
|
||||
|
||||
type upsertOIDCConnectorReq struct {
|
||||
Connector services.OIDCConnector `json:"connector"`
|
||||
TTL time.Duration `json:"ttl"`
|
||||
}
|
||||
|
||||
func (s *APIServer) upsertOIDCConnector(w http.ResponseWriter, r *http.Request, p httprouter.Params) (interface{}, error) {
|
||||
var req *upsertOIDCConnectorReq
|
||||
if err := httplib.ReadJSON(r, &req); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
err := s.a.UpsertOIDCConnector(req.Connector, req.TTL)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return message("ok"), nil
|
||||
}
|
||||
|
||||
func parseBool(q url.Values, name string) (bool, error) {
|
||||
stringVal := q.Get(name)
|
||||
if stringVal == "" {
|
||||
return false, nil
|
||||
}
|
||||
val, err := strconv.ParseBool(stringVal)
|
||||
if err != nil {
|
||||
return false, trace.Wrap(
|
||||
teleport.BadParameter(
|
||||
name, fmt.Sprintf("expected 'true' or 'false', got %v", stringVal)))
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func (s *APIServer) getOIDCConnector(w http.ResponseWriter, r *http.Request, p httprouter.Params) (interface{}, error) {
|
||||
withSecrets, err := parseBool(r.URL.Query(), "with_secrets")
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
connector, err := s.a.GetOIDCConnector(p[0].Value, withSecrets)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return connector, nil
|
||||
}
|
||||
|
||||
func (s *APIServer) deleteOIDCConnector(w http.ResponseWriter, r *http.Request, p httprouter.Params) (interface{}, error) {
|
||||
err := s.a.DeleteOIDCConnector(p[0].Value)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return message("ok"), nil
|
||||
}
|
||||
|
||||
func (s *APIServer) getOIDCConnectors(w http.ResponseWriter, r *http.Request, p httprouter.Params) (interface{}, error) {
|
||||
withSecrets, err := parseBool(r.URL.Query(), "with_secrets")
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
connectors, err := s.a.GetOIDCConnectors(withSecrets)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return connectors, nil
|
||||
}
|
||||
|
||||
type createOIDCAuthRequestReq struct {
|
||||
Req services.OIDCAuthRequest `json:"req"`
|
||||
}
|
||||
|
||||
func (s *APIServer) createOIDCAuthRequest(w http.ResponseWriter, r *http.Request, p httprouter.Params) (interface{}, error) {
|
||||
var req *createOIDCAuthRequestReq
|
||||
if err := httplib.ReadJSON(r, &req); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
response, err := s.a.CreateOIDCAuthRequest(req.Req)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
type validateOIDCAuthCallbackReq struct {
|
||||
Query url.Values `json:"query"`
|
||||
}
|
||||
|
||||
func (s *APIServer) validateOIDCAuthCallback(w http.ResponseWriter, r *http.Request, p httprouter.Params) (interface{}, error) {
|
||||
var req *validateOIDCAuthCallbackReq
|
||||
if err := httplib.ReadJSON(r, &req); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
response, err := s.a.ValidateOIDCAuthCallback(req.Query)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func message(msg string) map[string]interface{} {
|
||||
return map[string]interface{}{"message": msg}
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ type APISuite struct {
|
|||
LockS *services.LockService
|
||||
PresenceS *services.PresenceService
|
||||
ProvisioningS *services.ProvisioningService
|
||||
WebS *services.WebService
|
||||
WebS *services.IdentityService
|
||||
}
|
||||
|
||||
var _ = Suite(&APISuite{})
|
||||
|
@ -101,7 +101,7 @@ func (s *APISuite) SetUpTest(c *C) {
|
|||
s.LockS = services.NewLockService(s.bk)
|
||||
s.PresenceS = services.NewPresenceService(s.bk)
|
||||
s.ProvisioningS = services.NewProvisioningService(s.bk)
|
||||
s.WebS = services.NewWebService(s.bk)
|
||||
s.WebS = services.NewIdentityService(s.bk)
|
||||
}
|
||||
|
||||
func (s *APISuite) TearDownTest(c *C) {
|
||||
|
|
188
lib/auth/auth.go
188
lib/auth/auth.go
|
@ -25,16 +25,20 @@ package auth
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"net/url"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gravitational/teleport"
|
||||
"github.com/gravitational/teleport/lib/backend"
|
||||
"github.com/gravitational/teleport/lib/defaults"
|
||||
"github.com/gravitational/teleport/lib/services"
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/coreos/go-oidc/oauth2"
|
||||
"github.com/coreos/go-oidc/oidc"
|
||||
"github.com/gravitational/trace"
|
||||
"github.com/jonboulle/clockwork"
|
||||
)
|
||||
|
@ -86,10 +90,11 @@ func NewAuthServer(cfg *InitConfig, opts ...AuthServerOption) *AuthServer {
|
|||
LockService: services.NewLockService(cfg.Backend),
|
||||
PresenceService: services.NewPresenceService(cfg.Backend),
|
||||
ProvisioningService: services.NewProvisioningService(cfg.Backend),
|
||||
WebService: services.NewWebService(cfg.Backend),
|
||||
IdentityService: services.NewIdentityService(cfg.Backend),
|
||||
BkKeysService: services.NewBkKeysService(cfg.Backend),
|
||||
DomainName: cfg.DomainName,
|
||||
AuthServiceName: cfg.AuthServiceName,
|
||||
oidcClients: make(map[string]*oidc.Client),
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(&as)
|
||||
|
@ -109,8 +114,10 @@ func NewAuthServer(cfg *InitConfig, opts ...AuthServerOption) *AuthServer {
|
|||
// - same for users and their sessions
|
||||
// - checks public keys to see if they're signed by it (can be trusted or not)
|
||||
type AuthServer struct {
|
||||
clock clockwork.Clock
|
||||
bk backend.Backend
|
||||
sync.Mutex
|
||||
oidcClients map[string]*oidc.Client
|
||||
clock clockwork.Clock
|
||||
bk backend.Backend
|
||||
Authority
|
||||
|
||||
// DomainName stores the FQDN of the signing CA (its certificate will have this
|
||||
|
@ -126,7 +133,7 @@ type AuthServer struct {
|
|||
*services.LockService
|
||||
*services.PresenceService
|
||||
*services.ProvisioningService
|
||||
*services.WebService
|
||||
*services.IdentityService
|
||||
*services.BkKeysService
|
||||
}
|
||||
|
||||
|
@ -370,11 +377,11 @@ func (s *AuthServer) NewWebSession(userName string) (*Session, error) {
|
|||
}
|
||||
|
||||
func (s *AuthServer) UpsertWebSession(user string, sess *Session, ttl time.Duration) error {
|
||||
return s.WebService.UpsertWebSession(user, sess.ID, sess.WS, ttl)
|
||||
return s.IdentityService.UpsertWebSession(user, sess.ID, sess.WS, ttl)
|
||||
}
|
||||
|
||||
func (s *AuthServer) GetWebSession(userName string, id string) (*Session, error) {
|
||||
ws, err := s.WebService.GetWebSession(userName, id)
|
||||
ws, err := s.IdentityService.GetWebSession(userName, id)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
@ -390,7 +397,7 @@ func (s *AuthServer) GetWebSession(userName string, id string) (*Session, error)
|
|||
}
|
||||
|
||||
func (s *AuthServer) GetWebSessionInfo(userName string, id string) (*Session, error) {
|
||||
sess, err := s.WebService.GetWebSession(userName, id)
|
||||
sess, err := s.IdentityService.GetWebSession(userName, id)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
@ -407,7 +414,170 @@ func (s *AuthServer) GetWebSessionInfo(userName string, id string) (*Session, er
|
|||
}
|
||||
|
||||
func (s *AuthServer) DeleteWebSession(user string, id string) error {
|
||||
return trace.Wrap(s.WebService.DeleteWebSession(user, id))
|
||||
return trace.Wrap(s.IdentityService.DeleteWebSession(user, id))
|
||||
}
|
||||
|
||||
func (s *AuthServer) getOIDCClient(conn *services.OIDCConnector) (*oidc.Client, error) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
client, ok := s.oidcClients[conn.ID]
|
||||
if ok {
|
||||
return client, nil
|
||||
}
|
||||
|
||||
config := oidc.ClientConfig{
|
||||
RedirectURL: conn.RedirectURL,
|
||||
Credentials: oidc.ClientCredentials{
|
||||
ID: conn.ClientID,
|
||||
Secret: conn.ClientSecret,
|
||||
},
|
||||
}
|
||||
|
||||
client, err := oidc.NewClient(config)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
client.SyncProviderConfig(conn.IssuerURL)
|
||||
|
||||
s.oidcClients[conn.ID] = client
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (s *AuthServer) CreateOIDCAuthRequest(req services.OIDCAuthRequest) (*services.OIDCAuthRequest, error) {
|
||||
connector, err := s.IdentityService.GetOIDCConnector(req.ConnectorID, true)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
oidcClient, err := s.getOIDCClient(connector)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
token, err := utils.CryptoRandomHex(WebSessionTokenLenBytes)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
req.StateToken = token
|
||||
|
||||
oauthClient, err := oidcClient.OAuthClient()
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
sessionKey, err := utils.CryptoRandomHex(64)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
redirectURL := oauthClient.AuthCodeURL(sessionKey, "", "")
|
||||
req.RedirectURL = redirectURL
|
||||
|
||||
err = s.IdentityService.CreateOIDCAuthRequest(req, defaults.OIDCAuthRequestTTL)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return &req, nil
|
||||
}
|
||||
|
||||
// OIDCAuthResponse is returned when auth server validated callback parameters
|
||||
// returned from OIDC provider
|
||||
type OIDCAuthResponse struct {
|
||||
// User is authenticated teleport user
|
||||
User services.User `json:"user"`
|
||||
// Web session will be generated by auth server if requested in OIDCAuthRequest
|
||||
Session *Session `json:"session,omitempty"`
|
||||
// Cert will be generated by certificate authority
|
||||
Cert []byte `json:"cert,omitempty"`
|
||||
// Req is original oidc auth request
|
||||
Req services.OIDCAuthRequest `json:"req"`
|
||||
}
|
||||
|
||||
// ValidateOIDCAuthCallback is called by the proxy to check OIDC query parameters
|
||||
// returned by OIDC Provider, if everything checks out, auth server
|
||||
// will respond with OIDCAuthResponse, otherwise it will return error
|
||||
func (a *AuthServer) ValidateOIDCAuthCallback(q url.Values) (*OIDCAuthResponse, error) {
|
||||
if error := q.Get("error"); error != "" {
|
||||
return nil, trace.Wrap(teleport.NewOAuth2Error(
|
||||
oauth2.ErrorInvalidRequest, error, q))
|
||||
}
|
||||
|
||||
code := q.Get("code")
|
||||
if code == "" {
|
||||
return nil, trace.Wrap(teleport.NewOAuth2Error(
|
||||
oauth2.ErrorInvalidRequest, "code query param must be set", q))
|
||||
}
|
||||
|
||||
stateToken := q.Get("state")
|
||||
if stateToken == "" {
|
||||
return nil, trace.Wrap(teleport.NewOAuth2Error(
|
||||
oauth2.ErrorInvalidRequest, "missing state query param", q))
|
||||
}
|
||||
|
||||
req, err := a.IdentityService.GetOIDCAuthRequest(stateToken)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
connector, err := a.IdentityService.GetOIDCConnector(req.ConnectorID, true)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
oidcClient, err := a.getOIDCClient(connector)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
tok, err := oidcClient.ExchangeAuthCode(code)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(teleport.NewOAuth2Error(
|
||||
oauth2.ErrorUnsupportedResponseType,
|
||||
"unable to verify auth code with issuer", q))
|
||||
}
|
||||
|
||||
claims, err := tok.Claims()
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(teleport.NewOAuth2Error(
|
||||
oauth2.ErrorUnsupportedResponseType, "unable to construct claims", q))
|
||||
}
|
||||
|
||||
ident, err := oidc.IdentityFromClaims(claims)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(teleport.NewOAuth2Error(
|
||||
oauth2.ErrorUnsupportedResponseType, "unable to convert claims to identity", q))
|
||||
}
|
||||
|
||||
user, err := a.IdentityService.GetUserByOIDCIdentity(services.OIDCIdentity{
|
||||
ConnectorID: req.ConnectorID, Email: ident.Email})
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
response := &OIDCAuthResponse{
|
||||
User: *user,
|
||||
}
|
||||
|
||||
if req.CreateWebSession {
|
||||
sess, err := a.NewWebSession(user.Name)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
if err := a.UpsertWebSession(user.Name, sess, WebSessionTTL); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
response.Session = sess
|
||||
}
|
||||
|
||||
if len(req.PublicKey) != 0 {
|
||||
cert, err := a.GenerateUserCert(req.PublicKey, user.Name, req.CertTTL)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
response.Cert = cert
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
const (
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
package auth
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/gravitational/teleport"
|
||||
|
@ -343,11 +344,11 @@ func (a *AuthWithRoles) GenerateUserCert(key []byte, user string, ttl time.Durat
|
|||
return a.authServer.GenerateUserCert(key, user, ttl)
|
||||
}
|
||||
}
|
||||
func (a *AuthWithRoles) CreateSignupToken(user string, mappings []string) (token string, e error) {
|
||||
func (a *AuthWithRoles) CreateSignupToken(user services.User) (token string, e error) {
|
||||
if err := a.permChecker.HasPermission(a.role, ActionCreateSignupToken); err != nil {
|
||||
return "", trace.Wrap(err)
|
||||
} else {
|
||||
return a.authServer.CreateSignupToken(user, mappings)
|
||||
return a.authServer.CreateSignupToken(user)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -375,3 +376,57 @@ func (a *AuthWithRoles) UpsertUser(u services.User) error {
|
|||
return a.authServer.UpsertUser(u)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AuthWithRoles) UpsertOIDCConnector(connector services.OIDCConnector, ttl time.Duration) error {
|
||||
if err := a.permChecker.HasPermission(a.role, ActionUpsertOIDCConnector); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
return a.authServer.IdentityService.UpsertOIDCConnector(connector, ttl)
|
||||
}
|
||||
|
||||
func (a *AuthWithRoles) GetOIDCConnector(id string, withSecrets bool) (*services.OIDCConnector, error) {
|
||||
if withSecrets {
|
||||
if err := a.permChecker.HasPermission(a.role, ActionGetOIDCConnectorWithSecrets); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
} else {
|
||||
if err := a.permChecker.HasPermission(a.role, ActionGetOIDCConnectorWithoutSecrets); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
return a.authServer.IdentityService.GetOIDCConnector(id, withSecrets)
|
||||
}
|
||||
|
||||
func (a *AuthWithRoles) GetOIDCConnectors(withSecrets bool) ([]services.OIDCConnector, error) {
|
||||
if withSecrets {
|
||||
if err := a.permChecker.HasPermission(a.role, ActionGetOIDCConnectorsWithSecrets); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
} else {
|
||||
if err := a.permChecker.HasPermission(a.role, ActionGetOIDCConnectorsWithoutSecrets); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
return a.authServer.IdentityService.GetOIDCConnectors(withSecrets)
|
||||
}
|
||||
|
||||
func (a *AuthWithRoles) CreateOIDCAuthRequest(req services.OIDCAuthRequest) (*services.OIDCAuthRequest, error) {
|
||||
if err := a.permChecker.HasPermission(a.role, ActionCreateOIDCAuthRequest); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return a.authServer.CreateOIDCAuthRequest(req)
|
||||
}
|
||||
|
||||
func (a *AuthWithRoles) ValidateOIDCAuthCallback(q url.Values) (*OIDCAuthResponse, error) {
|
||||
if err := a.permChecker.HasPermission(a.role, ActionValidateOIDCAuthCallback); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return a.authServer.ValidateOIDCAuthCallback(q)
|
||||
}
|
||||
|
||||
func (a *AuthWithRoles) DeleteOIDCConnector(connectorID string) error {
|
||||
if err := a.permChecker.HasPermission(a.role, ActionDeleteOIDCConnector); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
return a.authServer.IdentityService.DeleteOIDCConnector(connectorID)
|
||||
}
|
||||
|
|
|
@ -608,16 +608,12 @@ func (c *Client) GenerateUserCert(
|
|||
|
||||
// CreateSignupToken creates one time token for creating account for the user
|
||||
// For each token it creates username and hotp generator
|
||||
func (c *Client) CreateSignupToken(user string, allowedLogins []string) (string, error) {
|
||||
if len(allowedLogins) == 0 {
|
||||
// TODO(klizhentas) do validation on the serverside
|
||||
return "", trace.Wrap(
|
||||
teleport.BadParameter("allowedUsers",
|
||||
"cannot create a new account without any allowed logins"))
|
||||
func (c *Client) CreateSignupToken(user services.User) (string, error) {
|
||||
if err := user.Check(); err != nil {
|
||||
return "", trace.Wrap(err)
|
||||
}
|
||||
out, err := c.PostJSON(c.Endpoint("signuptokens"), createSignupTokenReq{
|
||||
User: user,
|
||||
AllowedLogins: allowedLogins,
|
||||
User: user,
|
||||
})
|
||||
if err != nil {
|
||||
return "", trace.Wrap(err)
|
||||
|
@ -663,6 +659,82 @@ func (c *Client) CreateUserWithToken(token, password, hotpToken string) (*Sessio
|
|||
return sess, nil
|
||||
}
|
||||
|
||||
func (c *Client) UpsertOIDCConnector(connector services.OIDCConnector, ttl time.Duration) error {
|
||||
_, err := c.PostJSON(c.Endpoint("oidc", "connectors"), upsertOIDCConnectorReq{
|
||||
Connector: connector,
|
||||
TTL: ttl,
|
||||
})
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) GetOIDCConnector(id string, withSecrets bool) (*services.OIDCConnector, error) {
|
||||
if id == "" {
|
||||
return nil, trace.Wrap(teleport.BadParameter("id", "missing connector id"))
|
||||
}
|
||||
out, err := c.Get(c.Endpoint("oidc", "connectors", id),
|
||||
url.Values{"with_secrets": []string{fmt.Sprintf("%t", withSecrets)}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var conn *services.OIDCConnector
|
||||
if err := json.Unmarshal(out.Bytes(), &conn); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetOIDCConnectors(withSecrets bool) ([]services.OIDCConnector, error) {
|
||||
out, err := c.Get(c.Endpoint("oidc", "connectors"),
|
||||
url.Values{"with_secrets": []string{fmt.Sprintf("%t", withSecrets)}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var connectors []services.OIDCConnector
|
||||
if err := json.Unmarshal(out.Bytes(), &connectors); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return connectors, nil
|
||||
}
|
||||
|
||||
func (c *Client) DeleteOIDCConnector(connectorID string) error {
|
||||
if connectorID == "" {
|
||||
return trace.Wrap(teleport.BadParameter("id", "missing connector id"))
|
||||
}
|
||||
_, err := c.Delete(c.Endpoint("oidc", "connectors", connectorID))
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
func (c *Client) CreateOIDCAuthRequest(req services.OIDCAuthRequest) (*services.OIDCAuthRequest, error) {
|
||||
out, err := c.PostJSON(c.Endpoint("oidc", "requests", "create"), createOIDCAuthRequestReq{
|
||||
Req: req,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
var response *services.OIDCAuthRequest
|
||||
if err := json.Unmarshal(out.Bytes(), &response); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (c *Client) ValidateOIDCAuthCallback(q url.Values) (*OIDCAuthResponse, error) {
|
||||
out, err := c.PostJSON(c.Endpoint("oidc", "requests", "validate"), validateOIDCAuthCallbackReq{
|
||||
Query: q,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
var response *OIDCAuthResponse
|
||||
if err := json.Unmarshal(out.Bytes(), &response); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
type chunkRW struct {
|
||||
c *Client
|
||||
id string
|
||||
|
@ -743,4 +815,10 @@ type ClientI interface {
|
|||
GenerateUserCert(key []byte, user string, ttl time.Duration) ([]byte, error)
|
||||
GetSignupTokenData(token string) (user string, QRImg []byte, hotpFirstValues []string, e error)
|
||||
CreateUserWithToken(token, password, hotpToken string) (*Session, error)
|
||||
UpsertOIDCConnector(connector services.OIDCConnector, ttl time.Duration) error
|
||||
GetOIDCConnector(id string, withSecrets bool) (*services.OIDCConnector, error)
|
||||
GetOIDCConnectors(withSecrets bool) ([]services.OIDCConnector, error)
|
||||
DeleteOIDCConnector(connectorID string) error
|
||||
CreateOIDCAuthRequest(req services.OIDCAuthRequest) (*services.OIDCAuthRequest, error)
|
||||
ValidateOIDCAuthCallback(q url.Values) (*OIDCAuthResponse, error)
|
||||
}
|
||||
|
|
|
@ -68,6 +68,10 @@ type InitConfig struct {
|
|||
// in configuration, so auth server will init the tunnels on the first start
|
||||
ReverseTunnels []services.ReverseTunnel
|
||||
|
||||
// OIDCConnectors is a list of trusted OpenID Connect identity providers
|
||||
// in configuration, so auth server will init the tunnels on the first start
|
||||
OIDCConnectors []services.OIDCConnector
|
||||
|
||||
// HostCA is an optional host certificate authority keypair
|
||||
HostCA *services.CertAuthority
|
||||
|
||||
|
@ -193,6 +197,15 @@ func Init(cfg InitConfig) (*AuthServer, *Identity, error) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(cfg.OIDCConnectors) != 0 {
|
||||
log.Infof("FIRST START: Initializing oidc connectors")
|
||||
for _, connector := range cfg.OIDCConnectors {
|
||||
if err := asrv.UpsertOIDCConnector(connector, 0); err != nil {
|
||||
return nil, nil, trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
identity, err := initKeys(asrv, cfg.DataDir, IdentityID{HostUUID: cfg.HostUUID, Role: teleport.RoleAdmin})
|
||||
|
|
|
@ -34,7 +34,6 @@ import (
|
|||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/gokyle/hotp"
|
||||
"github.com/gravitational/configure/cstrings"
|
||||
"github.com/gravitational/trace"
|
||||
)
|
||||
|
||||
|
@ -42,19 +41,21 @@ import (
|
|||
// For each token it creates username and hotp generator
|
||||
//
|
||||
// allowedLogins are linux user logins allowed for the new user to use
|
||||
func (s *AuthServer) CreateSignupToken(user string, allowedLogins []string) (string, error) {
|
||||
if !cstrings.IsValidUnixUser(user) {
|
||||
return "", trace.Wrap(
|
||||
teleport.BadParameter("user", fmt.Sprintf("'%v' is not a valid user name", user)))
|
||||
func (s *AuthServer) CreateSignupToken(user services.User) (string, error) {
|
||||
if err := user.Check(); err != nil {
|
||||
return "", trace.Wrap(err)
|
||||
}
|
||||
for _, login := range allowedLogins {
|
||||
if !cstrings.IsValidUnixUser(login) {
|
||||
return "", trace.Wrap(teleport.BadParameter(
|
||||
"allowedLogins", fmt.Sprintf("'%v' is not a valid user name", login)))
|
||||
// make sure that connectors actually exist
|
||||
for _, id := range user.OIDCIdentities {
|
||||
if err := id.Check(); err != nil {
|
||||
return "", trace.Wrap(err)
|
||||
}
|
||||
if _, err := s.GetOIDCConnector(id.ConnectorID, false); err != nil {
|
||||
return "", trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
// check existing
|
||||
_, err := s.GetPasswordHash(user)
|
||||
_, err := s.GetPasswordHash(user.Name)
|
||||
if err == nil {
|
||||
return "", trace.Wrap(
|
||||
teleport.BadParameter(
|
||||
|
@ -71,7 +72,7 @@ func (s *AuthServer) CreateSignupToken(user string, allowedLogins []string) (str
|
|||
log.Errorf("[AUTH API] failed to generate HOTP: %v", err)
|
||||
return "", trace.Wrap(err)
|
||||
}
|
||||
otpQR, err := otp.QR("Teleport: " + user + "@" + s.AuthServiceName)
|
||||
otpQR, err := otp.QR("Teleport: " + user.Name + "@" + s.AuthServiceName)
|
||||
if err != nil {
|
||||
return "", trace.Wrap(err)
|
||||
}
|
||||
|
@ -92,7 +93,6 @@ func (s *AuthServer) CreateSignupToken(user string, allowedLogins []string) (str
|
|||
Hotp: otpMarshalled,
|
||||
HotpFirstValues: otpFirstValues,
|
||||
HotpQR: otpQR,
|
||||
AllowedLogins: allowedLogins,
|
||||
}
|
||||
|
||||
err = s.UpsertSignupToken(token, tokenData, defaults.MaxSignupTokenTTL)
|
||||
|
@ -100,7 +100,7 @@ func (s *AuthServer) CreateSignupToken(user string, allowedLogins []string) (str
|
|||
return "", trace.Wrap(err)
|
||||
}
|
||||
|
||||
log.Infof("[AUTH API] created the signup token for %v as %v", user, allowedLogins)
|
||||
log.Infof("[AUTH API] created the signup token for %v as %v", user)
|
||||
return token, nil
|
||||
}
|
||||
|
||||
|
@ -125,12 +125,12 @@ func (s *AuthServer) GetSignupTokenData(token string) (user string,
|
|||
return "", nil, nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
_, err = s.GetPasswordHash(tokenData.User)
|
||||
_, err = s.GetPasswordHash(tokenData.User.Name)
|
||||
if err == nil {
|
||||
return "", nil, nil, trace.Errorf("can't add user %v, user already exists", tokenData.User)
|
||||
}
|
||||
|
||||
return tokenData.User, tokenData.HotpQR, tokenData.HotpFirstValues, nil
|
||||
return tokenData.User.Name, tokenData.HotpQR, tokenData.HotpFirstValues, nil
|
||||
}
|
||||
|
||||
// CreateUserWithToken creates account with provided token and password.
|
||||
|
@ -164,33 +164,33 @@ func (s *AuthServer) CreateUserWithToken(token, password, hotpToken string) (*Se
|
|||
return nil, trace.Wrap(teleport.BadParameter("hotp", "wrong HOTP token"))
|
||||
}
|
||||
|
||||
_, _, err = s.UpsertPassword(tokenData.User, []byte(password))
|
||||
_, _, err = s.UpsertPassword(tokenData.User.Name, []byte(password))
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
// apply user allowed logins
|
||||
if err = s.UpsertUser(services.User{Name: tokenData.User, AllowedLogins: tokenData.AllowedLogins}); err != nil {
|
||||
if err = s.UpsertUser(tokenData.User); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
err = s.UpsertHOTP(tokenData.User, otp)
|
||||
err = s.UpsertHOTP(tokenData.User.Name, otp)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
log.Infof("[AUTH] created new user: %v as %v", tokenData.User, tokenData.AllowedLogins)
|
||||
log.Infof("[AUTH] created new user: %v", &tokenData.User)
|
||||
|
||||
if err = s.DeleteSignupToken(token); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
sess, err := s.NewWebSession(tokenData.User)
|
||||
sess, err := s.NewWebSession(tokenData.User.Name)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
err = s.UpsertWebSession(tokenData.User, sess, WebSessionTTL)
|
||||
err = s.UpsertWebSession(tokenData.User.Name, sess, WebSessionTTL)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
|
|
@ -78,20 +78,22 @@ func NewStandardPermissions() PermissionChecker {
|
|||
}
|
||||
|
||||
sp.permissions[teleport.RoleProxy] = map[string]bool{
|
||||
ActionGetChunkReader: true,
|
||||
ActionGetReverseTunnels: true,
|
||||
ActionGetServers: true,
|
||||
ActionGetEvents: true,
|
||||
ActionUpsertProxy: true,
|
||||
ActionGetProxies: true,
|
||||
ActionGetAuthServers: true,
|
||||
ActionGetCertAuthorities: true,
|
||||
ActionGetUsers: true,
|
||||
ActionGetLocalDomain: true,
|
||||
ActionGetUserKeys: true,
|
||||
ActionLogEntry: true,
|
||||
ActionGetSession: true,
|
||||
ActionGetSessions: true,
|
||||
ActionGetChunkReader: true,
|
||||
ActionGetReverseTunnels: true,
|
||||
ActionGetServers: true,
|
||||
ActionGetEvents: true,
|
||||
ActionUpsertProxy: true,
|
||||
ActionGetProxies: true,
|
||||
ActionGetAuthServers: true,
|
||||
ActionGetCertAuthorities: true,
|
||||
ActionGetUsers: true,
|
||||
ActionGetLocalDomain: true,
|
||||
ActionGetUserKeys: true,
|
||||
ActionLogEntry: true,
|
||||
ActionGetSession: true,
|
||||
ActionGetSessions: true,
|
||||
ActionCreateOIDCAuthRequest: true,
|
||||
ActionValidateOIDCAuthCallback: true,
|
||||
}
|
||||
|
||||
sp.permissions[teleport.RoleWeb] = map[string]bool{
|
||||
|
@ -214,4 +216,12 @@ const (
|
|||
ActionGetSignupTokenData = "GetSignupTokenData"
|
||||
ActionCreateUserWithToken = "CreateUserWithToken"
|
||||
ActionUpsertUser = "UpsertUser"
|
||||
ActionUpsertOIDCConnector = "UpsertOIDCConnector"
|
||||
ActionDeleteOIDCConnector = "DeleteOIDCConnector"
|
||||
ActionGetOIDCConnectorWithSecrets = "GetOIDCConnectorWithSecrets"
|
||||
ActionGetOIDCConnectorWithoutSecrets = "GetOIDCConnectorWithoutSecrets"
|
||||
ActionGetOIDCConnectorsWithSecrets = "GetOIDCConnectorsWithSecrets"
|
||||
ActionGetOIDCConnectorsWithoutSecrets = "GetOIDCConnectorsWithoutSecrets"
|
||||
ActionCreateOIDCAuthRequest = "CreateOIDCAuthRequest"
|
||||
ActionValidateOIDCAuthCallback = "ValidateOIDCAuthCallback"
|
||||
)
|
||||
|
|
|
@ -247,7 +247,7 @@ func (s *TunSuite) TestWebCreatingNewUser(c *C) {
|
|||
|
||||
// User will scan QRcode, here we just loads the OTP generator
|
||||
// right from the backend
|
||||
tokenData, err := s.a.WebService.GetSignupToken(token)
|
||||
tokenData, err := s.a.IdentityService.GetSignupToken(token)
|
||||
c.Assert(err, IsNil)
|
||||
otp, err := hotp.Unmarshal(tokenData.Hotp)
|
||||
c.Assert(err, IsNil)
|
||||
|
@ -257,7 +257,7 @@ func (s *TunSuite) TestWebCreatingNewUser(c *C) {
|
|||
hotpTokens[i] = otp.OTP()
|
||||
}
|
||||
|
||||
tokenData3, err := s.a.WebService.GetSignupToken(token3)
|
||||
tokenData3, err := s.a.IdentityService.GetSignupToken(token3)
|
||||
c.Assert(err, IsNil)
|
||||
otp3, err := hotp.Unmarshal(tokenData3.Hotp)
|
||||
c.Assert(err, IsNil)
|
||||
|
@ -292,7 +292,7 @@ func (s *TunSuite) TestWebCreatingNewUser(c *C) {
|
|||
_, err = clt2.CreateUserWithToken(token, "another_user_signup_attempt", hotpTokens[0])
|
||||
c.Assert(err, NotNil)
|
||||
|
||||
_, err = s.a.WebService.GetSignupToken(token)
|
||||
_, err = s.a.IdentityService.GetSignupToken(token)
|
||||
c.Assert(err, NotNil) // token was deleted
|
||||
|
||||
// token out of scan range
|
||||
|
|
|
@ -29,6 +29,7 @@ import (
|
|||
|
||||
"github.com/gravitational/teleport"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/gravitational/trace"
|
||||
"github.com/mailgun/timetools"
|
||||
|
@ -124,6 +125,7 @@ func (b *BoltBackend) CreateVal(bucket []string, key string, val []byte, ttl tim
|
|||
Value: val,
|
||||
TTL: ttl,
|
||||
}
|
||||
log.Infof("createVal: path=%v, key=%v, ttl=%v", bucket, key, ttl)
|
||||
bytes, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
|
@ -197,6 +199,7 @@ func (b *BoltBackend) CompareAndSwap(path []string, key string, val []byte, ttl
|
|||
}
|
||||
|
||||
func (b *BoltBackend) GetVal(path []string, key string) ([]byte, error) {
|
||||
log.Infof("createVal: path=%v key=%v", path, key)
|
||||
var val []byte
|
||||
if err := b.getKey(path, key, &val); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
|
|
|
@ -224,6 +224,15 @@ func ApplyFileConfig(fc *FileConfig, cfg *service.Config) error {
|
|||
cfg.ReverseTunnels = append(cfg.ReverseTunnels, *tun)
|
||||
}
|
||||
|
||||
// add oidc connectors supplied from configs
|
||||
for _, c := range fc.Auth.OIDCConnectors {
|
||||
conn, err := c.Parse()
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
cfg.OIDCConnectors = append(cfg.OIDCConnectors, *conn)
|
||||
}
|
||||
|
||||
// apply "proxy_service" section
|
||||
if fc.Proxy.ListenAddress != "" {
|
||||
addr, err := utils.ParseHostPortAddr(fc.Proxy.ListenAddress, int(defaults.SSHProxyListenPort))
|
||||
|
|
|
@ -91,6 +91,12 @@ var (
|
|||
"keys": true,
|
||||
"reverse_tunnels": true,
|
||||
"addresses": true,
|
||||
"oidc_connectors": true,
|
||||
"id": true,
|
||||
"issuer_url": true,
|
||||
"client_id": true,
|
||||
"client_secret": true,
|
||||
"redirect_url": true,
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -377,9 +383,12 @@ type Auth struct {
|
|||
// Authorities 3rd party authorities this auth service trusts.
|
||||
Authorities []Authority `yaml:"authorities,omitempty"`
|
||||
|
||||
// List of SSH tunnels to 3rd party proxy services (used to talk
|
||||
// ReverseTunnels is aist of SSH tunnels to 3rd party proxy services (used to talk
|
||||
// to 3rd party auth servers we trust)
|
||||
ReverseTunnels []ReverseTunnel `yaml:"reverse_tunnels,omitempty"`
|
||||
|
||||
// OIDCConnectors is a list of trusted OpenID Connect Identity providers
|
||||
OIDCConnectors []OIDCConnector `yaml:"oidc_connectors"`
|
||||
}
|
||||
|
||||
// SSH is 'ssh_service' section of the config file
|
||||
|
@ -516,3 +525,36 @@ func (a *Authority) Parse() (*services.CertAuthority, error) {
|
|||
|
||||
return ca, nil
|
||||
}
|
||||
|
||||
// OIDCConnector specifies configuration fo Open ID Connect compatible external
|
||||
// identity provider, e.g. google in some organisation
|
||||
type OIDCConnector struct {
|
||||
// ID is a provider id, 'e.g.' google, used internally
|
||||
ID string `yaml:"id"`
|
||||
// Issuer URL is the endpoint of the provider, e.g. https://accounts.google.com
|
||||
IssuerURL string `yaml:"issuer_url"`
|
||||
// ClientID is id for authentication client (in our case it's our Auth server)
|
||||
ClientID string `yaml:"client_id"`
|
||||
// ClientSecret is used to authenticate our client and should not
|
||||
// be visible to end user
|
||||
ClientSecret string `yaml:"client_secret"`
|
||||
// RedirectURL - Identity provider will use this URL to redirect
|
||||
// client's browser back to it after successfull authentication
|
||||
// Should match the URL on Provider's side
|
||||
RedirectURL string `yaml:"redirect_url"`
|
||||
}
|
||||
|
||||
// Parse parses config struct into services connector and checks if it's valid
|
||||
func (o *OIDCConnector) Parse() (*services.OIDCConnector, error) {
|
||||
other := &services.OIDCConnector{
|
||||
ID: o.ID,
|
||||
IssuerURL: o.IssuerURL,
|
||||
ClientID: o.ClientID,
|
||||
ClientSecret: o.ClientSecret,
|
||||
RedirectURL: o.RedirectURL,
|
||||
}
|
||||
if err := other.Check(); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return other, nil
|
||||
}
|
||||
|
|
|
@ -137,6 +137,9 @@ const (
|
|||
|
||||
// ActivePartyTTL is a TTL when party is marked as inactive
|
||||
ActivePartyTTL = 30 * time.Second
|
||||
|
||||
// OIDCAuthRequestTTL is TTL of internally stored auth request created by client
|
||||
OIDCAuthRequestTTL = 60 * time.Second
|
||||
)
|
||||
|
||||
// Default connection limits, they can be applied separately on any of the Teleport
|
||||
|
|
|
@ -81,6 +81,9 @@ type Config struct {
|
|||
// first cluster start
|
||||
ReverseTunnels []services.ReverseTunnel
|
||||
|
||||
// OIDCConnectors is a list of trusted OpenID Connect identity providers
|
||||
OIDCConnectors []services.OIDCConnector
|
||||
|
||||
// PidFile is a full path of the PID file for teleport daemon
|
||||
PIDFile string
|
||||
}
|
||||
|
|
|
@ -286,6 +286,7 @@ func (process *TeleportProcess) initAuthService() error {
|
|||
HostUUID: cfg.HostUUID,
|
||||
Authorities: cfg.Auth.Authorities,
|
||||
ReverseTunnels: cfg.ReverseTunnels,
|
||||
OIDCConnectors: cfg.OIDCConnectors,
|
||||
}
|
||||
authServer, identity, err := auth.Init(acfg)
|
||||
if err != nil {
|
||||
|
@ -579,7 +580,9 @@ func (process *TeleportProcess) initProxyEndpoint(conn *Connector) error {
|
|||
Proxy: tsrv,
|
||||
AssetsDir: cfg.Proxy.AssetsDir,
|
||||
AuthServers: cfg.AuthServers[0],
|
||||
DomainName: cfg.Hostname})
|
||||
DomainName: cfg.Hostname,
|
||||
ProxyClient: conn.Client,
|
||||
})
|
||||
if err != nil {
|
||||
utils.Consolef(cfg.Console, "[PROXY] starting the web server: %v", err)
|
||||
return trace.Wrap(err)
|
||||
|
|
|
@ -54,7 +54,7 @@ type ServicesTestSuite struct {
|
|||
LockS *LockService
|
||||
PresenceS *PresenceService
|
||||
ProvisioningS *ProvisioningService
|
||||
WebS *WebService
|
||||
WebS *IdentityService
|
||||
ChangesC chan interface{}
|
||||
}
|
||||
|
||||
|
@ -64,7 +64,7 @@ func NewServicesTestSuite(backend backend.Backend) *ServicesTestSuite {
|
|||
s.LockS = NewLockService(backend)
|
||||
s.PresenceS = NewPresenceService(backend)
|
||||
s.ProvisioningS = NewProvisioningService(backend)
|
||||
s.WebS = NewWebService(backend)
|
||||
s.WebS = NewIdentityService(backend)
|
||||
s.ChangesC = make(chan interface{})
|
||||
return &s
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ package services
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/gravitational/teleport"
|
||||
|
@ -34,6 +34,7 @@ import (
|
|||
"github.com/gravitational/configure/cstrings"
|
||||
"github.com/gravitational/trace"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// User is an optional user entry in the database
|
||||
|
@ -44,6 +45,38 @@ type User struct {
|
|||
// AllowedLogins represents a list of OS users this teleport
|
||||
// user is allowed to login as
|
||||
AllowedLogins []string `json:"allowed_logins"`
|
||||
|
||||
// OIDCIdentities lists associated OpenID Connect identities
|
||||
// that let user log in using externally verified identity
|
||||
OIDCIdentities []OIDCIdentity `json:"oidc_identities"`
|
||||
}
|
||||
|
||||
func (u *User) String() string {
|
||||
return fmt.Sprintf("User(name=%v, allowed_logins=%v, identities=%v)", u.Name, u.AllowedLogins, u.OIDCIdentities)
|
||||
}
|
||||
|
||||
// Check checks validity of all parameters
|
||||
func (u *User) Check() error {
|
||||
if !cstrings.IsValidUnixUser(u.Name) {
|
||||
return trace.Wrap(
|
||||
teleport.BadParameter("Name", fmt.Sprintf("'%v' is not a valid user name", u.Name)))
|
||||
}
|
||||
if len(u.AllowedLogins) == 0 {
|
||||
return trace.Wrap(teleport.BadParameter(
|
||||
"AllowedLogins", fmt.Sprintf("'%v' has no valid allowed logins", u.Name)))
|
||||
}
|
||||
for _, login := range u.AllowedLogins {
|
||||
if !cstrings.IsValidUnixUser(login) {
|
||||
return trace.Wrap(teleport.BadParameter(
|
||||
"login", fmt.Sprintf("'%v' is not a valid user name", login)))
|
||||
}
|
||||
}
|
||||
for _, id := range u.OIDCIdentities {
|
||||
if err := id.Check(); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AuthorizedKey is a public key that is authorized to access SSH
|
||||
|
@ -55,23 +88,21 @@ type AuthorizedKey struct {
|
|||
Value []byte `json:"value"`
|
||||
}
|
||||
|
||||
// WebService is responsible for managing web users and currently
|
||||
// IdentityService is responsible for managing web users and currently
|
||||
// user accounts as well
|
||||
type WebService struct {
|
||||
backend backend.Backend
|
||||
SignupMutex *sync.Mutex
|
||||
type IdentityService struct {
|
||||
backend backend.Backend
|
||||
}
|
||||
|
||||
// NewWebService returns new instance of WebService
|
||||
func NewWebService(backend backend.Backend) *WebService {
|
||||
return &WebService{
|
||||
backend: backend,
|
||||
SignupMutex: &sync.Mutex{},
|
||||
// NewIdentityService returns new instance of WebService
|
||||
func NewIdentityService(backend backend.Backend) *IdentityService {
|
||||
return &IdentityService{
|
||||
backend: backend,
|
||||
}
|
||||
}
|
||||
|
||||
// GetUsers returns a list of users registered with the local auth server
|
||||
func (s *WebService) GetUsers() ([]User, error) {
|
||||
func (s *IdentityService) GetUsers() ([]User, error) {
|
||||
keys, err := s.backend.GetKeys([]string{"web", "users"})
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
|
@ -88,22 +119,29 @@ func (s *WebService) GetUsers() ([]User, error) {
|
|||
}
|
||||
|
||||
// UpsertUser updates parameters about user
|
||||
func (s *WebService) UpsertUser(user User) error {
|
||||
func (s *IdentityService) UpsertUser(user User) error {
|
||||
if !cstrings.IsValidUnixUser(user.Name) {
|
||||
return trace.Wrap(
|
||||
teleport.BadParameter("user.Name", fmt.Sprintf("'%v is not a valid unix username'", user.Name)))
|
||||
}
|
||||
data, err := json.Marshal(user.AllowedLogins)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
for _, l := range user.AllowedLogins {
|
||||
if !cstrings.IsValidUnixUser(l) {
|
||||
return trace.Wrap(
|
||||
teleport.BadParameter("login", fmt.Sprintf("'%v is not a valid unix username'", l)))
|
||||
}
|
||||
}
|
||||
err = s.backend.UpsertVal([]string{"web", "users", user.Name}, "logins", []byte(data), backend.Forever)
|
||||
for _, i := range user.OIDCIdentities {
|
||||
if err := i.Check(); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
data, err := json.Marshal(user)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
err = s.backend.UpsertVal([]string{"web", "users", user.Name}, "params", []byte(data), backend.Forever)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
@ -111,23 +149,40 @@ func (s *WebService) UpsertUser(user User) error {
|
|||
}
|
||||
|
||||
// GetUser returns a user by name
|
||||
func (s *WebService) GetUser(user string) (*User, error) {
|
||||
func (s *IdentityService) GetUser(user string) (*User, error) {
|
||||
u := User{Name: user}
|
||||
data, err := s.backend.GetVal([]string{"web", "users", user}, "logins")
|
||||
data, err := s.backend.GetVal([]string{"web", "users", user}, "params")
|
||||
if err != nil {
|
||||
if teleport.IsNotFound(err) {
|
||||
return &u, nil
|
||||
}
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
if err := json.Unmarshal(data, &u.AllowedLogins); err != nil {
|
||||
if err := json.Unmarshal(data, &u); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return &u, nil
|
||||
}
|
||||
|
||||
// GetUserByOIDCIdentity returns a user by it's specified OIDC Identity, returns first
|
||||
// user specified with this identity
|
||||
func (s *IdentityService) GetUserByOIDCIdentity(id OIDCIdentity) (*User, error) {
|
||||
users, err := s.GetUsers()
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
for _, u := range users {
|
||||
for _, uid := range u.OIDCIdentities {
|
||||
if uid.Equals(&id) {
|
||||
return &u, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, trace.Wrap(teleport.NotFound(fmt.Sprintf("user with identity %v not found", &id)))
|
||||
}
|
||||
|
||||
// DeleteUser deletes a user with all the keys from the backend
|
||||
func (s *WebService) DeleteUser(user string) error {
|
||||
func (s *IdentityService) DeleteUser(user string) error {
|
||||
err := s.backend.DeleteBucket([]string{"web", "users"}, user)
|
||||
if err != nil {
|
||||
if teleport.IsNotFound(err) {
|
||||
|
@ -138,7 +193,7 @@ func (s *WebService) DeleteUser(user string) error {
|
|||
}
|
||||
|
||||
// UpsertPasswordHash upserts user password hash
|
||||
func (s *WebService) UpsertPasswordHash(user string, hash []byte) error {
|
||||
func (s *IdentityService) UpsertPasswordHash(user string, hash []byte) error {
|
||||
err := s.backend.UpsertVal([]string{"web", "users", user}, "pwd", hash, 0)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
|
@ -147,7 +202,7 @@ func (s *WebService) UpsertPasswordHash(user string, hash []byte) error {
|
|||
}
|
||||
|
||||
// GetPasswordHash returns the password hash for a given user
|
||||
func (s *WebService) GetPasswordHash(user string) ([]byte, error) {
|
||||
func (s *IdentityService) GetPasswordHash(user string) ([]byte, error) {
|
||||
hash, err := s.backend.GetVal([]string{"web", "users", user}, "pwd")
|
||||
if err != nil {
|
||||
if teleport.IsNotFound(err) {
|
||||
|
@ -159,7 +214,7 @@ func (s *WebService) GetPasswordHash(user string) ([]byte, error) {
|
|||
}
|
||||
|
||||
// UpsertHOTP upserts HOTP state for user
|
||||
func (s *WebService) UpsertHOTP(user string, otp *hotp.HOTP) error {
|
||||
func (s *IdentityService) UpsertHOTP(user string, otp *hotp.HOTP) error {
|
||||
bytes, err := hotp.Marshal(otp)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
|
@ -173,7 +228,7 @@ func (s *WebService) UpsertHOTP(user string, otp *hotp.HOTP) error {
|
|||
}
|
||||
|
||||
// GetHOTP gets HOTP token state for a user
|
||||
func (s *WebService) GetHOTP(user string) (*hotp.HOTP, error) {
|
||||
func (s *IdentityService) GetHOTP(user string) (*hotp.HOTP, error) {
|
||||
bytes, err := s.backend.GetVal([]string{"web", "users", user},
|
||||
"hotp")
|
||||
if err != nil {
|
||||
|
@ -190,7 +245,7 @@ func (s *WebService) GetHOTP(user string) (*hotp.HOTP, error) {
|
|||
}
|
||||
|
||||
// UpsertWebSession updates or inserts a web session for a user and session id
|
||||
func (s *WebService) UpsertWebSession(user, sid string, session WebSession, ttl time.Duration) error {
|
||||
func (s *IdentityService) UpsertWebSession(user, sid string, session WebSession, ttl time.Duration) error {
|
||||
bytes, err := json.Marshal(session)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
|
@ -204,7 +259,7 @@ func (s *WebService) UpsertWebSession(user, sid string, session WebSession, ttl
|
|||
}
|
||||
|
||||
// GetWebSession returns a web session state for a given user and session id
|
||||
func (s *WebService) GetWebSession(user, sid string) (*WebSession, error) {
|
||||
func (s *IdentityService) GetWebSession(user, sid string) (*WebSession, error) {
|
||||
val, err := s.backend.GetVal(
|
||||
[]string{"web", "users", user, "sessions"},
|
||||
sid,
|
||||
|
@ -223,7 +278,7 @@ func (s *WebService) GetWebSession(user, sid string) (*WebSession, error) {
|
|||
}
|
||||
|
||||
// GetWebSessionsKeys returns public keys associated with the session
|
||||
func (s *WebService) GetWebSessionsKeys(user string) ([]AuthorizedKey, error) {
|
||||
func (s *IdentityService) GetWebSessionsKeys(user string) ([]AuthorizedKey, error) {
|
||||
keys, err := s.backend.GetKeys([]string{"web", "users", user, "sessions"})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -241,7 +296,7 @@ func (s *WebService) GetWebSessionsKeys(user string) ([]AuthorizedKey, error) {
|
|||
}
|
||||
|
||||
// DeleteWebSession deletes web session from the storage
|
||||
func (s *WebService) DeleteWebSession(user, sid string) error {
|
||||
func (s *IdentityService) DeleteWebSession(user, sid string) error {
|
||||
err := s.backend.DeleteKey(
|
||||
[]string{"web", "users", user, "sessions"},
|
||||
sid,
|
||||
|
@ -250,7 +305,7 @@ func (s *WebService) DeleteWebSession(user, sid string) error {
|
|||
}
|
||||
|
||||
// UpsertPassword upserts new password and HOTP token
|
||||
func (s *WebService) UpsertPassword(user string,
|
||||
func (s *IdentityService) UpsertPassword(user string,
|
||||
password []byte) (hotpURL string, hotpQR []byte, err error) {
|
||||
|
||||
if err := verifyPassword(password); err != nil {
|
||||
|
@ -288,7 +343,7 @@ func (s *WebService) UpsertPassword(user string,
|
|||
}
|
||||
|
||||
// CheckPassword is called on web user or tsh user login
|
||||
func (s *WebService) CheckPassword(user string, password []byte, hotpToken string) error {
|
||||
func (s *IdentityService) CheckPassword(user string, password []byte, hotpToken string) error {
|
||||
if err := verifyPassword(password); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
@ -314,7 +369,7 @@ func (s *WebService) CheckPassword(user string, password []byte, hotpToken strin
|
|||
|
||||
// CheckPasswordWOToken checks just password without checking HOTP tokens
|
||||
// used in case of SSH authentication, when token has been validated
|
||||
func (s *WebService) CheckPasswordWOToken(user string, password []byte) error {
|
||||
func (s *IdentityService) CheckPasswordWOToken(user string, password []byte) error {
|
||||
if err := verifyPassword(password); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
@ -347,6 +402,129 @@ func verifyPassword(password []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// UpsertSignupToken upserts signup token - one time token that lets user to create a user account
|
||||
func (s *IdentityService) UpsertSignupToken(token string, tokenData SignupToken, ttl time.Duration) error {
|
||||
if ttl < time.Second || ttl > defaults.MaxSignupTokenTTL {
|
||||
ttl = defaults.MaxSignupTokenTTL
|
||||
}
|
||||
out, err := json.Marshal(tokenData)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
err = s.backend.UpsertVal(userTokensPath, token, out, ttl)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// GetSignupToken returns signup token data
|
||||
func (s *IdentityService) GetSignupToken(token string) (*SignupToken, error) {
|
||||
out, err := s.backend.GetVal(userTokensPath, token)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
var data *SignupToken
|
||||
err = json.Unmarshal(out, &data)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// DeleteSignupToken deletes signup token from the storage
|
||||
func (s *IdentityService) DeleteSignupToken(token string) error {
|
||||
err := s.backend.DeleteKey(userTokensPath, token)
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
// UpsertOIDCConnector upserts OIDC Connector
|
||||
func (s *IdentityService) UpsertOIDCConnector(connector OIDCConnector, ttl time.Duration) error {
|
||||
if err := connector.Check(); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
data, err := json.Marshal(connector)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
err = s.backend.UpsertVal(connectorsPath, connector.ID, data, ttl)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteOIDCConnector deletes OIDC Connector
|
||||
func (s *IdentityService) DeleteOIDCConnector(connectorID string) error {
|
||||
err := s.backend.DeleteKey(connectorsPath, connectorID)
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
// GetOIDCConnector returns OIDC connector data, , withSecrets adds or removes client secret from return results
|
||||
func (s *IdentityService) GetOIDCConnector(id string, withSecrets bool) (*OIDCConnector, error) {
|
||||
out, err := s.backend.GetVal(connectorsPath, id)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
var data *OIDCConnector
|
||||
err = json.Unmarshal(out, &data)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
if !withSecrets {
|
||||
data.ClientSecret = ""
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// GetOIDCConnectors returns registered connectors, withSecrets adds or removes client secret from return results
|
||||
func (s *IdentityService) GetOIDCConnectors(withSecrets bool) ([]OIDCConnector, error) {
|
||||
connectorIDs, err := s.backend.GetKeys(connectorsPath)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
connectors := make([]OIDCConnector, 0, len(connectorIDs))
|
||||
for _, id := range connectorIDs {
|
||||
connector, err := s.GetOIDCConnector(id, withSecrets)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
connectors = append(connectors, *connector)
|
||||
}
|
||||
return connectors, nil
|
||||
}
|
||||
|
||||
// CreateOIDCAuthRequest creates new auth request
|
||||
func (s *IdentityService) CreateOIDCAuthRequest(req OIDCAuthRequest, ttl time.Duration) error {
|
||||
if err := req.Check(); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
data, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
err = s.backend.CreateVal(authRequestsPath, req.StateToken, data, ttl)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetOIDCAuthRequest returns OIDC auth request if found
|
||||
func (s *IdentityService) GetOIDCAuthRequest(stateToken string) (*OIDCAuthRequest, error) {
|
||||
data, err := s.backend.GetVal(authRequestsPath, stateToken)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
var req *OIDCAuthRequest
|
||||
if err := json.Unmarshal(data, &req); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// WebSession stores key and value used to authenticate with SSH
|
||||
// notes on behalf of user
|
||||
type WebSession struct {
|
||||
|
@ -365,51 +543,135 @@ type WebSession struct {
|
|||
// is stored and generated when tctl add user is executed
|
||||
type SignupToken struct {
|
||||
Token string `json:"token"`
|
||||
User string `json:"user"`
|
||||
User User `json:"user"`
|
||||
Hotp []byte `json:"hotp"`
|
||||
HotpFirstValues []string `json:"hotp_first_values"`
|
||||
HotpQR []byte `json:"hotp_qr"`
|
||||
AllowedLogins []string `json:"allowed_logins"`
|
||||
}
|
||||
|
||||
// OIDCConnector specifies configuration fo Open ID Connect compatible external
|
||||
// identity provider, e.g. google in some organisation
|
||||
type OIDCConnector struct {
|
||||
// ID is a provider id, 'e.g.' google, used internally
|
||||
ID string `json:"id"`
|
||||
// Issuer URL is the endpoint of the provider, e.g. https://accounts.google.com
|
||||
IssuerURL string `json:"issuer_url"`
|
||||
// ClientID is id for authentication client (in our case it's our Auth server)
|
||||
ClientID string `json:"client_id"`
|
||||
// ClientSecret is used to authenticate our client and should not
|
||||
// be visible to end user
|
||||
ClientSecret string `json:"client_secret"`
|
||||
// RedirectURL - Identity provider will use this URL to redirect
|
||||
// client's browser back to it after successfull authentication
|
||||
// Should match the URL on Provider's side
|
||||
RedirectURL string `json:"redirect_url"`
|
||||
}
|
||||
|
||||
// Check returns nil if all parameters are great, err otherwise
|
||||
func (o *OIDCConnector) Check() error {
|
||||
if o.ID == "" {
|
||||
return trace.Wrap(teleport.BadParameter("ID", "missing connector id"))
|
||||
}
|
||||
if _, err := url.Parse(o.IssuerURL); err != nil {
|
||||
return trace.Wrap(teleport.BadParameter("IssuerURL", fmt.Sprintf("bad url: '%v'", o.IssuerURL)))
|
||||
}
|
||||
if _, err := url.Parse(o.RedirectURL); err != nil {
|
||||
return trace.Wrap(teleport.BadParameter("RedirectURL", fmt.Sprintf("bad url: '%v'", o.RedirectURL)))
|
||||
}
|
||||
if o.ClientID == "" {
|
||||
return trace.Wrap(teleport.BadParameter("ClientID", "missing client id"))
|
||||
}
|
||||
if o.ClientSecret == "" {
|
||||
return trace.Wrap(teleport.BadParameter("ClientID", "missing client secret"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
userTokensPath = []string{"addusertokens"}
|
||||
userTokensPath = []string{"addusertokens"}
|
||||
connectorsPath = []string{"web", "connectors", "oidc", "connectors"}
|
||||
authRequestsPath = []string{"web", "connectors", "oidc", "requests"}
|
||||
)
|
||||
|
||||
// UpsertSignupToken upserts signup token - one time token that lets user to create a user account
|
||||
func (s *WebService) UpsertSignupToken(token string, tokenData SignupToken, ttl time.Duration) error {
|
||||
if ttl < time.Second || ttl > defaults.MaxSignupTokenTTL {
|
||||
ttl = defaults.MaxSignupTokenTTL
|
||||
}
|
||||
out, err := json.Marshal(tokenData)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
// OIDCIdentity is OpenID Connect identity that is linked
|
||||
// to particular user and connector and lets user to log in using external
|
||||
// credentials, e.g. google
|
||||
type OIDCIdentity struct {
|
||||
// ConnectorID is id of registered OIDC connector, e.g. 'google-example.com'
|
||||
ConnectorID string `json:"connector_id"`
|
||||
|
||||
err = s.backend.UpsertVal(userTokensPath, token, out, ttl)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
// Email is OIDC verified email claim
|
||||
// e.g. bob@example.com
|
||||
Email string `json:"username"`
|
||||
}
|
||||
|
||||
// String returns debug friendly representation of this identity
|
||||
func (i *OIDCIdentity) String() string {
|
||||
return fmt.Sprintf("OIDCIdentity(connectorID=%v, email=%v)", i.ConnectorID, i.Email)
|
||||
}
|
||||
|
||||
// Equals returns true if this identity equals to passed one
|
||||
func (i *OIDCIdentity) Equals(other *OIDCIdentity) bool {
|
||||
return i.ConnectorID == other.ConnectorID && i.Email == other.Email
|
||||
}
|
||||
|
||||
// Check returns nil if all parameters are great, err otherwise
|
||||
func (i *OIDCIdentity) Check() error {
|
||||
if i.ConnectorID == "" {
|
||||
return trace.Wrap(teleport.BadParameter("ConnectorID", "missing value"))
|
||||
}
|
||||
if i.Email == "" {
|
||||
return trace.Wrap(teleport.BadParameter("Email", "missing email"))
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// GetSignupToken returns signup token data
|
||||
func (s *WebService) GetSignupToken(token string) (*SignupToken, error) {
|
||||
out, err := s.backend.GetVal(userTokensPath, token)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
// OIDCAuthRequest is a request to authenticate with OIDC
|
||||
// provider, the state about request is managed by auth server
|
||||
type OIDCAuthRequest struct {
|
||||
// ConnectorID is ID of OIDC connector this request uses
|
||||
ConnectorID string `json:"connector_id"`
|
||||
|
||||
// StateToken is generated by service and is used to validate
|
||||
// reuqest coming from
|
||||
StateToken string `json:"state_token"`
|
||||
|
||||
// ClientStateToken is passed by console client
|
||||
ClientStateToken string `json:"client_state_token"`
|
||||
|
||||
// RedirectURL will be used by browser
|
||||
RedirectURL string `json:"redirect_url"`
|
||||
|
||||
// PublicKey is an optional public key, users want these
|
||||
// keys to be signed by auth servers user CA in case
|
||||
// of successfull auth
|
||||
PublicKey []byte `json:"public_key"`
|
||||
|
||||
// CertTTL is the TTL of the certificate user wants to get
|
||||
CertTTL time.Duration `json:"cert_ttl"`
|
||||
|
||||
// CreateWebSession indicates if user wants to generate a web
|
||||
// session after successful authentication
|
||||
CreateWebSession bool `json:"create_web_session"`
|
||||
}
|
||||
|
||||
// Check returns nil if all parameters are great, err otherwise
|
||||
func (i *OIDCAuthRequest) Check() error {
|
||||
if i.ConnectorID == "" {
|
||||
return trace.Wrap(teleport.BadParameter("ConnectorID", "missing value"))
|
||||
}
|
||||
var data *SignupToken
|
||||
err = json.Unmarshal(out, &data)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
if i.StateToken == "" {
|
||||
return trace.Wrap(teleport.BadParameter("StateToken", "missing value"))
|
||||
}
|
||||
if len(i.PublicKey) != 0 {
|
||||
_, _, _, _, err := ssh.ParseAuthorizedKey(i.PublicKey)
|
||||
if err != nil {
|
||||
return trace.Wrap(teleport.BadParameter("PublicKey", fmt.Sprintf("bad key: %v", err)))
|
||||
}
|
||||
if (i.CertTTL > defaults.MaxCertDuration) || (i.CertTTL < defaults.MinCertDuration) {
|
||||
return trace.Wrap(teleport.BadParameter("CertTTL", "wrong certificate TTL"))
|
||||
}
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// DeleteSignupToken deletes signup token from the storage
|
||||
func (s *WebService) DeleteSignupToken(token string) error {
|
||||
err := s.backend.DeleteKey(userTokensPath, token)
|
||||
return trace.Wrap(err)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -48,11 +48,11 @@ import (
|
|||
|
||||
// Handler is HTTP web proxy handler
|
||||
type Handler struct {
|
||||
httprouter.Router
|
||||
cfg Config
|
||||
auth *sessionCache
|
||||
sites *ttlmap.TtlMap
|
||||
sync.Mutex
|
||||
httprouter.Router
|
||||
cfg Config
|
||||
auth *sessionCache
|
||||
sites *ttlmap.TtlMap
|
||||
sessionStreamPollPeriod time.Duration
|
||||
}
|
||||
|
||||
|
@ -85,6 +85,8 @@ type Config struct {
|
|||
AuthServers utils.NetAddr
|
||||
// DomainName is a domain name served by web handler
|
||||
DomainName string
|
||||
// ProxyClient is a client that authenticated as proxy
|
||||
ProxyClient auth.ClientI
|
||||
}
|
||||
|
||||
// Version is a current webapi version
|
||||
|
@ -153,6 +155,10 @@ func NewHandler(cfg Config, opts ...HandlerOption) (http.Handler, error) {
|
|||
// get session chunks count
|
||||
h.GET("/webapi/sites/:site/sessions/:sid/chunkscount", h.withSiteAuth(h.siteSessionGetChunksCount))
|
||||
|
||||
// OIDC related callback handlers
|
||||
h.GET("/webapi/oidc/login", httplib.MakeHandler(h.oidcLogin))
|
||||
h.GET("/webapi/oidc/callback", httplib.MakeHandler(h.oidcCallback))
|
||||
|
||||
routingHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/" {
|
||||
http.Redirect(w, r, "/web", http.StatusFound)
|
||||
|
@ -168,6 +174,29 @@ func NewHandler(cfg Config, opts ...HandlerOption) (http.Handler, error) {
|
|||
return routingHandler, nil
|
||||
}
|
||||
|
||||
func (m *Handler) oidcLogin(w http.ResponseWriter, r *http.Request, p httprouter.Params) (interface{}, error) {
|
||||
log.Infof("oidcLogin start")
|
||||
response, err := m.cfg.ProxyClient.CreateOIDCAuthRequest(
|
||||
services.OIDCAuthRequest{
|
||||
ConnectorID: "google",
|
||||
CreateWebSession: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
http.Redirect(w, r, response.RedirectURL, http.StatusFound)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *Handler) oidcCallback(w http.ResponseWriter, r *http.Request, p httprouter.Params) (interface{}, error) {
|
||||
log.Infof("oidcLogin validate")
|
||||
response, err := m.cfg.ProxyClient.ValidateOIDCAuthCallback(r.URL.Query())
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// createSessionReq is a request to create session from username, password and second
|
||||
// factor token
|
||||
type createSessionReq struct {
|
||||
|
|
|
@ -51,6 +51,7 @@ type UserCommand struct {
|
|||
config *service.Config
|
||||
login string
|
||||
allowedLogins string
|
||||
identity string
|
||||
}
|
||||
|
||||
type NodeCommand struct {
|
||||
|
@ -113,6 +114,8 @@ func main() {
|
|||
userAdd.Arg("login", "Teleport user login").Required().StringVar(&cmdUsers.login)
|
||||
userAdd.Arg("local-logins", "Local UNIX users this account can log in as [login]").
|
||||
Default("").StringVar(&cmdUsers.allowedLogins)
|
||||
userAdd.Arg("external-auth", "External authentication methods, e.g. google:bob@gmail.com for google SSO").
|
||||
Default("").StringVar(&cmdUsers.identity)
|
||||
userAdd.Alias(AddUserHelp)
|
||||
|
||||
// list users command
|
||||
|
@ -256,7 +259,18 @@ func (u *UserCommand) Add(hostname string, client *auth.TunClient) error {
|
|||
if u.allowedLogins == "" {
|
||||
u.allowedLogins = u.login
|
||||
}
|
||||
token, err := client.CreateSignupToken(u.login, strings.Split(u.allowedLogins, ","))
|
||||
user := services.User{
|
||||
Name: u.login,
|
||||
AllowedLogins: strings.Split(u.allowedLogins, ","),
|
||||
}
|
||||
if u.identity != "" {
|
||||
vals := strings.Split(u.identity, ":")
|
||||
if len(vals) != 2 {
|
||||
return trace.Errorf("expected connector:email for external auth method, e.g. google:alice@gmail.com")
|
||||
}
|
||||
user.OIDCIdentities = []services.OIDCIdentity{{ConnectorID: vals[0], Email: vals[1]}}
|
||||
}
|
||||
token, err := client.CreateSignupToken(user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue