mirror of
https://github.com/gravitational/teleport
synced 2024-10-21 01:34:01 +00:00
Merge branch 'master' into ev/docker
This commit is contained in:
commit
bc85683b51
16
constants.go
16
constants.go
|
@ -72,18 +72,24 @@ const (
|
|||
// pining each other with it:
|
||||
KeepAliveReqType = "keepalive@openssh.com"
|
||||
|
||||
// OTP means One-time Password Algorithm.
|
||||
// OTP means One-time Password Algorithm for Two-Factor Authentication.
|
||||
OTP = "otp"
|
||||
|
||||
// TOTP means Time-based One-time Password Algorithm.
|
||||
// TOTP means Time-based One-time Password Algorithm. for Two-Factor Authentication.
|
||||
TOTP = "totp"
|
||||
|
||||
// HOTP means HMAC-based One-time Password Algorithm.
|
||||
// HOTP means HMAC-based One-time Password Algorithm.for Two-Factor Authentication.
|
||||
HOTP = "hotp"
|
||||
|
||||
// U2F means Universal 2nd Factor.
|
||||
// U2F means Universal 2nd Factor.for Two-Factor Authentication.
|
||||
U2F = "u2f"
|
||||
|
||||
// OIDC means OpenID Connect.
|
||||
// OFF means no second factor.for Two-Factor Authentication.
|
||||
OFF = "off"
|
||||
|
||||
// Local means authentication will happen locally within the Teleport cluster.
|
||||
Local = "local"
|
||||
|
||||
// OIDC means authentication will happen remotly using an OIDC connector.
|
||||
OIDC = "oidc"
|
||||
)
|
||||
|
|
|
@ -197,6 +197,13 @@ auth_service:
|
|||
# Turns 'auth' role on. Default is 'yes'
|
||||
enabled: yes
|
||||
|
||||
# defines the types and second factors the auth server supports
|
||||
authentication:
|
||||
# type can be local or oidc
|
||||
type: local
|
||||
# second_factor can be off, otp, or u2f
|
||||
second_factor: otp
|
||||
|
||||
# IP and the port to bind to. Other Teleport nodes will be connecting to
|
||||
# this port (AKA "Auth API" or "Cluster API") to validate client
|
||||
# certificates
|
||||
|
@ -747,14 +754,20 @@ OIDC integration with applications like Teleport.
|
|||
|
||||
```
|
||||
auth_service:
|
||||
enabled: true
|
||||
cluster_name: magadan
|
||||
oidc_connectors:
|
||||
- id: google
|
||||
redirect_url: https://localhost:3080/v1/webapi/oidc/callback
|
||||
client_id: id-from-google.apps.googleusercontent.com
|
||||
client_secret: secret-key-from-google
|
||||
issuer_url: https://accounts.google.com
|
||||
authentication:
|
||||
type: oidc
|
||||
oidc:
|
||||
id: google
|
||||
redirect_url: https://localhost:3080/v1/webapi/oidc/callback
|
||||
client_id: id-from-google.apps.googleusercontent.com
|
||||
client_secret: secret-key-from-google
|
||||
issuer_url: https://accounts.google.com
|
||||
display: "whaterver"
|
||||
scope: ["ssh_permissions", "roles"]
|
||||
claims_to_roles:
|
||||
- claim: role
|
||||
value: admin
|
||||
roles: ["dba", "backup", "root"]
|
||||
```
|
||||
|
||||
Now you should be able to create Teleport users whose identity is managed by Google.
|
||||
|
@ -817,17 +830,17 @@ service configuration in `teleport.yaml`:
|
|||
|
||||
```bash
|
||||
auth_service:
|
||||
u2f:
|
||||
# Must be set to 'yes' to enable and 'no' to disable:
|
||||
enabled: yes
|
||||
authentication:
|
||||
type: local
|
||||
second_factor: u2f
|
||||
u2f:
|
||||
# Only matters when multiple proxy servers are used:
|
||||
app_id: https://mycorp.com/appid.js
|
||||
|
||||
# Only matters when multiple proxy servers are used:
|
||||
app_id: https://mycorp.com/appid.js
|
||||
|
||||
# U2F facets must be set to Teleport proxy servers:
|
||||
facets:
|
||||
- https://proxy1.mycorp.com:3080
|
||||
- https://proxy2.mycorp.com:3080
|
||||
# U2F facets must be set to Teleport proxy servers:
|
||||
facets:
|
||||
- https://proxy1.mycorp.com:3080
|
||||
- https://proxy2.mycorp.com:3080
|
||||
```
|
||||
|
||||
If your Teleport is deployed with the same `teleport` process running as a proxy
|
||||
|
|
|
@ -117,14 +117,6 @@ func NewAPIServer(config *APIConfig) http.Handler {
|
|||
srv.GET("/:version/namespaces/:namespace/sessions/:id/stream", srv.withAuth(srv.getSessionChunk))
|
||||
srv.GET("/:version/namespaces/:namespace/sessions/:id/events", srv.withAuth(srv.getSessionEvents))
|
||||
|
||||
// OIDC
|
||||
srv.POST("/:version/oidc/connectors", srv.withAuth(srv.upsertOIDCConnector))
|
||||
srv.GET("/:version/oidc/connectors", srv.withAuth(srv.getOIDCConnectors))
|
||||
srv.GET("/:version/oidc/connectors/:id", srv.withAuth(srv.getOIDCConnector))
|
||||
srv.DELETE("/:version/oidc/connectors/:id", srv.withAuth(srv.deleteOIDCConnector))
|
||||
srv.POST("/:version/oidc/requests/create", srv.withAuth(srv.createOIDCAuthRequest))
|
||||
srv.POST("/:version/oidc/requests/validate", srv.withAuth(srv.validateOIDCAuthCallback))
|
||||
|
||||
// Namespaces
|
||||
srv.POST("/:version/namespaces", srv.withAuth(srv.upsertNamespace))
|
||||
srv.GET("/:version/namespaces", srv.withAuth(srv.getNamespaces))
|
||||
|
@ -137,6 +129,20 @@ func NewAPIServer(config *APIConfig) http.Handler {
|
|||
srv.GET("/:version/roles/:role", srv.withAuth(srv.getRole))
|
||||
srv.DELETE("/:version/roles/:role", srv.withAuth(srv.deleteRole))
|
||||
|
||||
// cluster authentication preferences
|
||||
srv.GET("/:version/authentication/preference", srv.withAuth(srv.getClusterAuthPreference))
|
||||
srv.POST("/:version/authentication/preference", srv.withAuth(srv.setClusterAuthPreference))
|
||||
srv.GET("/:version/authentication/preference/u2f", srv.withAuth(srv.getUniversalSecondFactor))
|
||||
srv.POST("/:version/authentication/preference/u2f", srv.withAuth(srv.setUniversalSecondFactor))
|
||||
|
||||
// OIDC
|
||||
srv.POST("/:version/oidc/connectors", srv.withAuth(srv.upsertOIDCConnector))
|
||||
srv.GET("/:version/oidc/connectors", srv.withAuth(srv.getOIDCConnectors))
|
||||
srv.GET("/:version/oidc/connectors/:id", srv.withAuth(srv.getOIDCConnector))
|
||||
srv.DELETE("/:version/oidc/connectors/:id", srv.withAuth(srv.deleteOIDCConnector))
|
||||
srv.POST("/:version/oidc/requests/create", srv.withAuth(srv.createOIDCAuthRequest))
|
||||
srv.POST("/:version/oidc/requests/validate", srv.withAuth(srv.validateOIDCAuthCallback))
|
||||
|
||||
// U2F
|
||||
srv.GET("/:version/u2f/signuptokens/:token", srv.withAuth(srv.getSignupU2FRegisterRequest))
|
||||
srv.POST("/:version/u2f/users", srv.withAuth(srv.createUserWithU2FToken))
|
||||
|
@ -750,12 +756,13 @@ func (s *APIServer) getDomainName(auth ClientI, w http.ResponseWriter, r *http.R
|
|||
|
||||
// getU2FAppID returns the U2F AppID in the auth configuration
|
||||
func (s *APIServer) getU2FAppID(auth ClientI, w http.ResponseWriter, r *http.Request, p httprouter.Params, version string) (interface{}, error) {
|
||||
appID, err := auth.GetU2FAppID()
|
||||
universalSecondFactor, err := auth.GetUniversalSecondFactor()
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/fido.trusted-apps+json")
|
||||
return appID, nil
|
||||
return universalSecondFactor.GetAppID(), nil
|
||||
}
|
||||
|
||||
func (s *APIServer) deleteCertAuthority(auth ClientI, w http.ResponseWriter, r *http.Request, p httprouter.Params, version string) (interface{}, error) {
|
||||
|
@ -1257,6 +1264,74 @@ func (s *APIServer) deleteRole(auth ClientI, w http.ResponseWriter, r *http.Requ
|
|||
return message(fmt.Sprintf("role '%v' deleted", role)), nil
|
||||
}
|
||||
|
||||
func (s *APIServer) getClusterAuthPreference(auth ClientI, w http.ResponseWriter, r *http.Request, p httprouter.Params, version string) (interface{}, error) {
|
||||
cap, err := auth.GetClusterAuthPreference()
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
return rawMessage(services.GetAuthPreferenceMarshaler().Marshal(cap, services.WithVersion(version)))
|
||||
}
|
||||
|
||||
type setClusterAuthPreferenceReq struct {
|
||||
ClusterAuthPreference json.RawMessage `json:"cluster_auth_prerference"`
|
||||
}
|
||||
|
||||
func (s *APIServer) setClusterAuthPreference(auth ClientI, w http.ResponseWriter, r *http.Request, p httprouter.Params, version string) (interface{}, error) {
|
||||
var req *setClusterAuthPreferenceReq
|
||||
|
||||
err := httplib.ReadJSON(r, &req)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
cap, err := services.GetAuthPreferenceMarshaler().Unmarshal(req.ClusterAuthPreference)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
err = auth.SetClusterAuthPreference(cap)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
return message(fmt.Sprintf("cluster authenticaton preference set: %+v", cap)), nil
|
||||
}
|
||||
|
||||
func (s *APIServer) getUniversalSecondFactor(auth ClientI, w http.ResponseWriter, r *http.Request, p httprouter.Params, version string) (interface{}, error) {
|
||||
universalSecondFactor, err := auth.GetUniversalSecondFactor()
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
return rawMessage(services.GetUniversalSecondFactorMarshaler().Marshal(universalSecondFactor, services.WithVersion(version)))
|
||||
}
|
||||
|
||||
type setUniversalSecondFactorReq struct {
|
||||
UniversalSecondFactor json.RawMessage `json:"universal_second_factor"`
|
||||
}
|
||||
|
||||
func (s *APIServer) setUniversalSecondFactor(auth ClientI, w http.ResponseWriter, r *http.Request, p httprouter.Params, version string) (interface{}, error) {
|
||||
var req *setUniversalSecondFactorReq
|
||||
|
||||
err := httplib.ReadJSON(r, &req)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
universalSecondFactor, err := services.GetUniversalSecondFactorMarshaler().Unmarshal(req.UniversalSecondFactor)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
err = auth.SetUniversalSecondFactor(universalSecondFactor)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
return message(fmt.Sprintf("universal second factor set: %+v", universalSecondFactor)), nil
|
||||
}
|
||||
|
||||
func message(msg string) map[string]interface{} {
|
||||
return map[string]interface{}{"message": msg}
|
||||
}
|
||||
|
|
|
@ -85,19 +85,27 @@ func NewAuthServer(cfg *InitConfig, opts ...AuthServerOption) *AuthServer {
|
|||
if cfg.Access == nil {
|
||||
cfg.Access = local.NewAccessService(cfg.Backend)
|
||||
}
|
||||
if cfg.ClusterAuthPreferenceService == nil {
|
||||
cfg.ClusterAuthPreferenceService = local.NewClusterAuthPreferenceService(cfg.Backend)
|
||||
}
|
||||
if cfg.UniversalSecondFactorService == nil {
|
||||
cfg.UniversalSecondFactorService = local.NewUniversalSecondFactorService(cfg.Backend)
|
||||
}
|
||||
|
||||
as := AuthServer{
|
||||
bk: cfg.Backend,
|
||||
Authority: cfg.Authority,
|
||||
Trust: cfg.Trust,
|
||||
Presence: cfg.Presence,
|
||||
Provisioner: cfg.Provisioner,
|
||||
Identity: cfg.Identity,
|
||||
Access: cfg.Access,
|
||||
DomainName: cfg.DomainName,
|
||||
AuthServiceName: cfg.AuthServiceName,
|
||||
oidcClients: make(map[string]*oidcClient),
|
||||
StaticTokens: cfg.StaticTokens,
|
||||
U2F: cfg.U2F,
|
||||
bk: cfg.Backend,
|
||||
Authority: cfg.Authority,
|
||||
Trust: cfg.Trust,
|
||||
Presence: cfg.Presence,
|
||||
Provisioner: cfg.Provisioner,
|
||||
Identity: cfg.Identity,
|
||||
Access: cfg.Access,
|
||||
DomainName: cfg.DomainName,
|
||||
AuthServiceName: cfg.AuthServiceName,
|
||||
StaticTokens: cfg.StaticTokens,
|
||||
ClusterAuthPreference: cfg.ClusterAuthPreferenceService,
|
||||
UniversalSecondFactorSettings: cfg.UniversalSecondFactorService,
|
||||
oidcClients: make(map[string]*oidcClient),
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(&as)
|
||||
|
@ -135,14 +143,13 @@ type AuthServer struct {
|
|||
// environments where paranoid security is not needed
|
||||
StaticTokens []services.ProvisionToken
|
||||
|
||||
// U2F is the configuration of the U2F 2 factor authentication
|
||||
U2F services.U2F
|
||||
|
||||
services.Trust
|
||||
services.Presence
|
||||
services.Provisioner
|
||||
services.Identity
|
||||
services.Access
|
||||
services.ClusterAuthPreference
|
||||
services.UniversalSecondFactorSettings
|
||||
}
|
||||
|
||||
func (a *AuthServer) Close() error {
|
||||
|
@ -158,20 +165,6 @@ func (a *AuthServer) GetDomainName() (string, error) {
|
|||
return a.DomainName, nil
|
||||
}
|
||||
|
||||
func (a *AuthServer) GetU2FAppID() (string, error) {
|
||||
if err := a.CheckU2FEnabled(); err != nil {
|
||||
return "", trace.Wrap(err)
|
||||
}
|
||||
return a.U2F.AppID, nil
|
||||
}
|
||||
|
||||
func (a *AuthServer) CheckU2FEnabled() error {
|
||||
if !a.U2F.Enabled {
|
||||
return trace.AccessDenied("U2F is disabled")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenerateHostCert uses the private key of the CA to sign the public key of the host
|
||||
// (along with meta data like host ID, node name, roles, and ttl) to generate a host certificate.
|
||||
func (s *AuthServer) GenerateHostCert(hostPublicKey []byte, hostID, nodeName, clusterName string, roles teleport.Roles, ttl time.Duration) ([]byte, error) {
|
||||
|
@ -296,7 +289,7 @@ func (s *AuthServer) PreAuthenticatedSignIn(user string) (services.WebSession, e
|
|||
}
|
||||
|
||||
func (s *AuthServer) U2FSignRequest(user string, password []byte) (*u2f.SignRequest, error) {
|
||||
err := s.CheckU2FEnabled()
|
||||
universalSecondFactor, err := s.GetUniversalSecondFactor()
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
@ -313,7 +306,7 @@ func (s *AuthServer) U2FSignRequest(user string, password []byte) (*u2f.SignRequ
|
|||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
challenge, err := u2f.NewChallenge(s.U2F.AppID, s.U2F.Facets)
|
||||
challenge, err := u2f.NewChallenge(universalSecondFactor.GetAppID(), universalSecondFactor.GetFacets())
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
@ -329,7 +322,8 @@ func (s *AuthServer) U2FSignRequest(user string, password []byte) (*u2f.SignRequ
|
|||
}
|
||||
|
||||
func (s *AuthServer) CheckU2FSignResponse(user string, response *u2f.SignResponse) error {
|
||||
err := s.CheckU2FEnabled()
|
||||
// before trying to register a user, see U2F is actually setup on the backend
|
||||
_, err := s.GetUniversalSecondFactor()
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
|
|
@ -122,11 +122,6 @@ func (a *AuthWithRoles) GetDomainName() (string, error) {
|
|||
return a.authServer.GetDomainName()
|
||||
}
|
||||
|
||||
func (a *AuthWithRoles) GetU2FAppID() (string, error) {
|
||||
// authenticated users can all read this
|
||||
return a.authServer.GetU2FAppID()
|
||||
}
|
||||
|
||||
func (a *AuthWithRoles) DeleteCertAuthority(id services.CertAuthID) error {
|
||||
if err := a.action(defaults.Namespace, services.KindCertAuthority, services.ActionWrite); err != nil {
|
||||
return trace.Wrap(err)
|
||||
|
@ -578,6 +573,42 @@ func (a *AuthWithRoles) DeleteRole(name string) error {
|
|||
return a.authServer.DeleteRole(name)
|
||||
}
|
||||
|
||||
func (a *AuthWithRoles) GetClusterAuthPreference() (services.AuthPreference, error) {
|
||||
err := a.action(defaults.Namespace, services.KindClusterAuthPreference, services.ActionRead)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
return a.authServer.GetClusterAuthPreference()
|
||||
}
|
||||
|
||||
func (a *AuthWithRoles) SetClusterAuthPreference(cap services.AuthPreference) error {
|
||||
err := a.action(defaults.Namespace, services.KindClusterAuthPreference, services.ActionWrite)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
return a.authServer.SetClusterAuthPreference(cap)
|
||||
}
|
||||
|
||||
func (a *AuthWithRoles) GetUniversalSecondFactor() (services.UniversalSecondFactor, error) {
|
||||
err := a.action(defaults.Namespace, services.KindUniversalSecondFactor, services.ActionRead)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
return a.authServer.GetUniversalSecondFactor()
|
||||
}
|
||||
|
||||
func (a *AuthWithRoles) SetUniversalSecondFactor(u2f services.UniversalSecondFactor) error {
|
||||
err := a.action(defaults.Namespace, services.KindUniversalSecondFactor, services.ActionWrite)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
return a.authServer.SetUniversalSecondFactor(u2f)
|
||||
}
|
||||
|
||||
// NewAuthWithRoles creates new auth server with access control
|
||||
func NewAuthWithRoles(authServer *AuthServer,
|
||||
checker services.AccessChecker,
|
||||
|
|
|
@ -1131,6 +1131,62 @@ func (c *Client) DeleteRole(name string) error {
|
|||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
func (c *Client) GetClusterAuthPreference() (services.AuthPreference, error) {
|
||||
out, err := c.Get(c.Endpoint("authentication", "preferences"), url.Values{})
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
cap, err := services.GetAuthPreferenceMarshaler().Unmarshal(out.Bytes())
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
return cap, nil
|
||||
}
|
||||
|
||||
func (c *Client) SetClusterAuthPreference(cap services.AuthPreference) error {
|
||||
data, err := services.GetAuthPreferenceMarshaler().Marshal(cap)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
_, err = c.PostJSON(c.Endpoint("authentication", "preferences"), &setClusterAuthPreferenceReq{ClusterAuthPreference: data})
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) GetUniversalSecondFactor() (services.UniversalSecondFactor, error) {
|
||||
out, err := c.Get(c.Endpoint("authentication", "preferences", "u2f"), url.Values{})
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
universalSecondFactor, err := services.GetUniversalSecondFactorMarshaler().Unmarshal(out.Bytes())
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
return universalSecondFactor, nil
|
||||
}
|
||||
|
||||
func (c *Client) SetUniversalSecondFactor(universalSecondFactor services.UniversalSecondFactor) error {
|
||||
data, err := services.GetUniversalSecondFactorMarshaler().Marshal(universalSecondFactor)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
_, err = c.PostJSON(c.Endpoint("authentication", "preferences", "u2f"), &setUniversalSecondFactorReq{UniversalSecondFactor: data})
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WebService implements features used by Web UI clients
|
||||
type WebService interface {
|
||||
// GetWebSessionInfo checks if a web sesion is valid, returns session id in case if
|
||||
|
@ -1145,7 +1201,7 @@ type WebService interface {
|
|||
DeleteWebSession(user string, sid string) error
|
||||
}
|
||||
|
||||
// IdentityService manages identities and userse
|
||||
// IdentityService manages identities and users
|
||||
type IdentityService interface {
|
||||
// UpsertPassword updates web access password for the user
|
||||
UpsertPassword(user string, password []byte) error
|
||||
|
@ -1180,9 +1236,6 @@ type IdentityService interface {
|
|||
// PreAuthenticatedSignIn is used get web session for a user that is already authenticated
|
||||
PreAuthenticatedSignIn(user string) (services.WebSession, error)
|
||||
|
||||
// GetU2FAppID returns U2F settings, like App ID and Facets
|
||||
GetU2FAppID() (string, error)
|
||||
|
||||
// GetUser returns user by name
|
||||
GetUser(name string) (services.User, error)
|
||||
|
||||
|
@ -1270,6 +1323,8 @@ type ClientI interface {
|
|||
services.Access
|
||||
WebService
|
||||
session.Service
|
||||
services.ClusterAuthPreference
|
||||
services.UniversalSecondFactorSettings
|
||||
|
||||
GetDomainName() (string, error)
|
||||
}
|
||||
|
|
|
@ -87,6 +87,12 @@ type InitConfig struct {
|
|||
// Access is service controlling access to resources
|
||||
Access services.Access
|
||||
|
||||
// ClusterAuthPreferenceService is a service to get and set authentication preferences.
|
||||
ClusterAuthPreferenceService services.ClusterAuthPreference
|
||||
|
||||
// UniversalSecondFactorService is a service to get and set universal second factor settings.
|
||||
UniversalSecondFactorService services.UniversalSecondFactorSettings
|
||||
|
||||
// Roles is a set of roles to create
|
||||
Roles []services.Role
|
||||
|
||||
|
@ -94,8 +100,12 @@ type InitConfig struct {
|
|||
// environments where paranoid security is not needed
|
||||
StaticTokens []services.ProvisionToken
|
||||
|
||||
// U2F is the configuration of the U2F 2 factor authentication
|
||||
U2F services.U2F
|
||||
// AuthPreference defines the authentication type (local, oidc) and second
|
||||
// factor (off, otp, u2f) passed in from a configuration file.
|
||||
AuthPreference services.AuthPreference
|
||||
|
||||
// U2F defines U2F application ID and any facets passed in from a configuration file.
|
||||
U2F services.UniversalSecondFactor
|
||||
}
|
||||
|
||||
// Init instantiates and configures an instance of AuthServer
|
||||
|
@ -126,6 +136,43 @@ func Init(cfg InitConfig, seedConfig bool) (*AuthServer, *Identity, error) {
|
|||
// and this is NOT the first time teleport starts on this machine
|
||||
skipConfig := seedConfig && !firstStart
|
||||
|
||||
// set the cluster auth prerference, the first time read them from the config
|
||||
// and create a resource on the backend, after that always read from the backend
|
||||
if !skipConfig {
|
||||
log.Infof("Initializing Cluster Authentication Preference: %+v", cfg.AuthPreference)
|
||||
err = asrv.SetClusterAuthPreference(cfg.AuthPreference)
|
||||
if err != nil {
|
||||
return nil, nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
log.Infof("Initializing Universal Second Factor Settings: %+v", cfg.U2F)
|
||||
if cfg.U2F != nil {
|
||||
err = asrv.SetUniversalSecondFactor(cfg.U2F)
|
||||
if err != nil {
|
||||
return nil, nil, trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// migrate: if U2F is set, but we don't have anything set on the backend, do a migration
|
||||
// because the old configuration file format is probably being used.
|
||||
if cfg.U2F != nil {
|
||||
// check if we already have something set on the backend
|
||||
_, err = asrv.GetUniversalSecondFactor()
|
||||
if err != nil {
|
||||
// if we have an error other than IsNotFound return it right away
|
||||
if !trace.IsNotFound(err) {
|
||||
return nil, nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
// if the error was not found, then try and set it on the backend
|
||||
err = asrv.SetUniversalSecondFactor(cfg.U2F)
|
||||
if err != nil {
|
||||
return nil, nil, trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add trusted authorities from the configuration into the trust backend:
|
||||
keepMap := make(map[string]int, 0)
|
||||
if !skipConfig {
|
||||
|
|
|
@ -17,15 +17,20 @@ limitations under the License.
|
|||
package auth
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/gravitational/teleport"
|
||||
"github.com/gravitational/teleport/lib/auth/testauthority"
|
||||
"github.com/gravitational/teleport/lib/backend"
|
||||
"github.com/gravitational/teleport/lib/backend/boltbk"
|
||||
"github.com/gravitational/teleport/lib/services"
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
|
@ -137,3 +142,36 @@ func (s *AuthInitSuite) TestBadIdentity(c *C) {
|
|||
_, err = ReadIdentityFromKeyPair(priv, cert)
|
||||
c.Assert(trace.IsBadParameter(err), Equals, true, Commentf("%#v", err))
|
||||
}
|
||||
|
||||
// TestAuthPreference ensures that the act of creating an AuthServer sets
|
||||
// the AuthPreference (type and second factor) on the backend.
|
||||
func (s *AuthInitSuite) TestAuthPreference(c *C) {
|
||||
tempDir, err := ioutil.TempDir("", "auth-test-")
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
bk, err := boltbk.New(backend.Params{"path": tempDir})
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
ap, err := services.NewAuthPreference(services.AuthPreferenceSpecV2{
|
||||
Type: "local",
|
||||
SecondFactor: "u2f",
|
||||
})
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
ac := InitConfig{
|
||||
DataDir: tempDir,
|
||||
HostUUID: "00000000-0000-0000-0000-000000000000",
|
||||
NodeName: "foo",
|
||||
Backend: bk,
|
||||
Authority: testauthority.New(),
|
||||
DomainName: "me.localhost",
|
||||
AuthPreference: ap,
|
||||
}
|
||||
as, _, err := Init(ac, true)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
cap, err := as.GetClusterAuthPreference()
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(cap.GetType(), Equals, "local")
|
||||
c.Assert(cap.GetSecondFactor(), Equals, "u2f")
|
||||
}
|
||||
|
|
|
@ -131,7 +131,7 @@ func (s *AuthServer) GetSignupTokenData(token string) (user string, qrCode []byt
|
|||
}
|
||||
|
||||
func (s *AuthServer) CreateSignupU2FRegisterRequest(token string) (u2fRegisterRequest *u2f.RegisterRequest, e error) {
|
||||
err := s.CheckU2FEnabled()
|
||||
universalSecondFactor, err := s.GetUniversalSecondFactor()
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
@ -143,10 +143,10 @@ func (s *AuthServer) CreateSignupU2FRegisterRequest(token string) (u2fRegisterRe
|
|||
|
||||
_, err = s.GetPasswordHash(tokenData.User.Name)
|
||||
if err == nil {
|
||||
return nil, trace.AlreadyExists("can't add user %v, user already exists", tokenData.User)
|
||||
return nil, trace.AlreadyExists("can't add user %q, user already exists", tokenData.User)
|
||||
}
|
||||
|
||||
c, err := u2f.NewChallenge(s.U2F.AppID, s.U2F.Facets)
|
||||
c, err := u2f.NewChallenge(universalSecondFactor.GetAppID(), universalSecondFactor.GetFacets())
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
@ -220,9 +220,10 @@ func (s *AuthServer) CreateUserWithToken(token string, password string, otpToken
|
|||
}
|
||||
|
||||
func (s *AuthServer) CreateUserWithU2FToken(token string, password string, response u2f.RegisterResponse) (services.WebSession, error) {
|
||||
err := s.CheckU2FEnabled()
|
||||
// before trying to create a user, see U2F is actually setup on the backend
|
||||
_, err := s.GetUniversalSecondFactor()
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tokenData, err := s.GetSignupToken(token)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2015-16 Gravitational, Inc.
|
||||
Copyright 2015 Gravitational, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -93,7 +93,7 @@ func ReadConfigFile(cliConfigPath string) (*FileConfig, error) {
|
|||
return ReadFromFile(configFilePath)
|
||||
}
|
||||
|
||||
// ApplyFileConfig applies confniguration from a YAML file to Teleport
|
||||
// ApplyFileConfig applies configuration from a YAML file to Teleport
|
||||
// runtime config
|
||||
func ApplyFileConfig(fc *FileConfig, cfg *service.Config) error {
|
||||
// no config file? no problem
|
||||
|
@ -141,13 +141,6 @@ func ApplyFileConfig(fc *FileConfig, cfg *service.Config) error {
|
|||
cfg.ApplyToken(fc.AuthToken)
|
||||
cfg.Auth.DomainName = fc.Auth.DomainName
|
||||
|
||||
// U2F (universal 2nd factor auth) configuration:
|
||||
u2f, err := fc.Auth.U2F.Parse()
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
cfg.Auth.U2F = *u2f
|
||||
|
||||
if fc.Global.DataDir != "" {
|
||||
cfg.DataDir = fc.Global.DataDir
|
||||
cfg.Auth.StorageConfig.Params["path"] = cfg.DataDir
|
||||
|
@ -229,15 +222,6 @@ 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))
|
||||
|
@ -273,6 +257,63 @@ func ApplyFileConfig(fc *FileConfig, cfg *service.Config) error {
|
|||
cfg.Proxy.TLSCert = fc.Proxy.CertFile
|
||||
}
|
||||
|
||||
// Deprecated: Use U2F section in Authentication section instead.
|
||||
u2f, err := fc.Auth.U2F.Parse()
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
if u2f.Enabled {
|
||||
log.Warnf("Configuration of U2F settings under auth_server.u2f has been deprecated.")
|
||||
log.Warnf("Configuration of U2F has moved under the auth_server.authentication section.")
|
||||
log.Warnf("See the Admin Guide for more details.\n")
|
||||
}
|
||||
// If the old config format is still being used, convert it to the new format,
|
||||
// and still allow it to be used.
|
||||
cfg.Auth.U2F, err = services.NewUniversalSecondFactor(services.UniversalSecondFactorSpecV2{
|
||||
AppID: u2f.AppID,
|
||||
Facets: u2f.Facets,
|
||||
})
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
// Deprecated: Use OIDC section in Authentication section instead.
|
||||
// If the old config format is still being used, allow it to be used.
|
||||
if len(fc.Auth.OIDCConnectors) > 0 {
|
||||
log.Warnf("Configuration for OIDC connectors under auth_server.oidc_connectors has been deprecated.")
|
||||
log.Warnf("Configuration for OIDC connectors has moved under the auth_server.authentication section.")
|
||||
log.Warnf("See the Admin Guide for more details.\n")
|
||||
}
|
||||
for _, c := range fc.Auth.OIDCConnectors {
|
||||
conn, err := c.Parse()
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
cfg.OIDCConnectors = append(cfg.OIDCConnectors, conn)
|
||||
}
|
||||
|
||||
// if authentication section exists on the auth_server, then use it to extract
|
||||
// the cluster authentication preferences and override u2f and oidc settings set
|
||||
// in the above two blocks
|
||||
if fc.Auth.Authentication != nil {
|
||||
authPreference, oidcConnector, universalSecondFactor, err := fc.Auth.Authentication.Parse()
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
cfg.Auth.Preference = authPreference
|
||||
|
||||
// we now only always allow a single oidc connector, note this will
|
||||
// override anything set with the old format
|
||||
if oidcConnector != nil {
|
||||
cfg.OIDCConnectors = []services.OIDCConnector{oidcConnector}
|
||||
}
|
||||
|
||||
// set u2f settings, note this will override anything set with the old format
|
||||
if universalSecondFactor != nil {
|
||||
cfg.Auth.U2F = universalSecondFactor
|
||||
}
|
||||
}
|
||||
|
||||
// apply "auth_service" section
|
||||
if fc.Auth.ListenAddress != "" {
|
||||
addr, err := utils.ParseHostPortAddr(fc.Auth.ListenAddress, int(defaults.AuthListenPort))
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2016 Gravitational, Inc.
|
||||
Copyright 2015 Gravitational, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -97,7 +97,6 @@ func (s *ConfigTestSuite) TestSampleConfig(c *check.C) {
|
|||
c.Assert(fc.Limits.MaxUsers, check.Equals, defaults.LimiterMaxConcurrentUsers)
|
||||
c.Assert(fc.Global.DataDir, check.Equals, defaults.DataDir)
|
||||
c.Assert(fc.Logger.Severity, check.Equals, "INFO")
|
||||
|
||||
}
|
||||
|
||||
func (s *ConfigTestSuite) TestConfigReading(c *check.C) {
|
||||
|
@ -298,6 +297,21 @@ func (s *ConfigTestSuite) TestApplyConfig(c *check.C) {
|
|||
c.Assert(cfg.Proxy.ReverseTunnelListenAddr.FullAddress(), check.Equals, "tcp://tunnelhost:1001")
|
||||
}
|
||||
|
||||
// TestLegacyU2FTransformation ensures that the legacy format for U2F gets transformed
|
||||
// into the new format that we are using now for backward compatibility.
|
||||
func (s *ConfigTestSuite) TestLegacyU2FTransformation(c *check.C) {
|
||||
conf, err := ReadConfig(bytes.NewBufferString(LegacyAuthenticationSection))
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(conf, check.NotNil)
|
||||
|
||||
cfg := service.MakeDefaultConfig()
|
||||
err = ApplyFileConfig(conf, cfg)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
c.Assert(cfg.Auth.U2F.GetAppID(), check.Equals, "https://graviton:3080")
|
||||
c.Assert(cfg.Auth.U2F.GetFacets(), check.DeepEquals, []string{"https://graviton:3080"})
|
||||
}
|
||||
|
||||
func checkStaticConfig(c *check.C, conf *FileConfig) {
|
||||
c.Assert(conf.AuthToken, check.Equals, "xxxyyy")
|
||||
c.Assert(conf.SSH.Enabled(), check.Equals, false) // YAML treats 'no' as False
|
||||
|
@ -419,117 +433,3 @@ func makeConfigFixture() string {
|
|||
|
||||
return conf.DebugDumpToYAML()
|
||||
}
|
||||
|
||||
const (
|
||||
StaticConfigString = `
|
||||
#
|
||||
# Some comments
|
||||
#
|
||||
teleport:
|
||||
nodename: edsger.example.com
|
||||
advertise_ip: 10.10.10.1
|
||||
pid_file: /var/run/teleport.pid
|
||||
auth_servers:
|
||||
- auth0.server.example.org:3024
|
||||
- auth1.server.example.org:3024
|
||||
auth_token: xxxyyy
|
||||
log:
|
||||
output: stderr
|
||||
severity: INFO
|
||||
storage:
|
||||
type: etcd
|
||||
peers: ['one', 'two']
|
||||
tls_key_file: /tls.key
|
||||
tls_cert_file: /tls.cert
|
||||
tls_ca_file: /tls.ca
|
||||
connection_limits:
|
||||
max_connections: 90
|
||||
max_users: 91
|
||||
rates:
|
||||
- period: 1m1s
|
||||
average: 70
|
||||
burst: 71
|
||||
- period: 10m10s
|
||||
average: 170
|
||||
burst: 171
|
||||
keys:
|
||||
- cert: node.cert
|
||||
private_key: !!binary cHJpdmF0ZSBrZXk=
|
||||
- cert_file: /proxy.cert.file
|
||||
private_key_file: /proxy.key.file
|
||||
|
||||
auth_service:
|
||||
enabled: yes
|
||||
listen_addr: auth:3025
|
||||
tokens:
|
||||
- "proxy,node:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
- "auth:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
authorities:
|
||||
- type: host
|
||||
domain_name: example.com
|
||||
checking_keys:
|
||||
- checking key 1
|
||||
checking_key_files:
|
||||
- /ca.checking.key
|
||||
signing_keys:
|
||||
- !!binary c2lnbmluZyBrZXkgMQ==
|
||||
signing_key_files:
|
||||
- /ca.signing.key
|
||||
reverse_tunnels:
|
||||
- domain_name: tunnel.example.com
|
||||
addresses: ["com-1", "com-2"]
|
||||
- domain_name: tunnel.example.org
|
||||
addresses: ["org-1"]
|
||||
|
||||
ssh_service:
|
||||
enabled: no
|
||||
listen_addr: ssh:3025
|
||||
labels:
|
||||
name: mongoserver
|
||||
role: slave
|
||||
commands:
|
||||
- name: hostname
|
||||
command: [/bin/hostname]
|
||||
period: 10ms
|
||||
- name: date
|
||||
command: [/bin/date]
|
||||
period: 20ms
|
||||
`
|
||||
SmallConfigString = `
|
||||
teleport:
|
||||
nodename: cat.example.com
|
||||
advertise_ip: 10.10.10.1
|
||||
pid_file: /var/run/teleport.pid
|
||||
auth_servers:
|
||||
- auth0.server.example.org:3024
|
||||
- auth1.server.example.org:3024
|
||||
auth_token: xxxyyy
|
||||
log:
|
||||
output: stderr
|
||||
severity: INFO
|
||||
connection_limits:
|
||||
max_connections: 90
|
||||
max_users: 91
|
||||
rates:
|
||||
- period: 1m1s
|
||||
average: 70
|
||||
burst: 71
|
||||
- period: 10m10s
|
||||
average: 170
|
||||
burst: 171
|
||||
auth_service:
|
||||
enabled: yes
|
||||
listen_addr: 10.5.5.1:3025
|
||||
cluster_name: magadan
|
||||
tokens:
|
||||
- "proxy,node:xxx"
|
||||
- "auth:yyy"
|
||||
ssh_service:
|
||||
enabled: no
|
||||
|
||||
proxy_service:
|
||||
enabled: yes
|
||||
web_listen_addr: webhost
|
||||
tunnel_listen_addr: tunnelhost:1001
|
||||
`
|
||||
)
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2015-16 Gravitational, Inc.
|
||||
Copyright 2015 Gravitational, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -107,6 +107,12 @@ var (
|
|||
"u2f": true,
|
||||
"app_id": true,
|
||||
"facets": true,
|
||||
"authentication": true,
|
||||
"second_factor": false,
|
||||
"oidc": true,
|
||||
"display": false,
|
||||
"scope": false,
|
||||
"claims_to_roles": true,
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -236,10 +242,6 @@ func MakeSampleFileConfig() (fc *FileConfig) {
|
|||
a.EnabledFlag = "yes"
|
||||
a.StaticTokens = []StaticToken{"proxy,node:cluster-join-token"}
|
||||
|
||||
a.U2F.EnabledFlag = "yes"
|
||||
a.U2F.AppID = conf.Auth.U2F.AppID
|
||||
a.U2F.Facets = conf.Auth.U2F.Facets
|
||||
|
||||
// sample proxy config:
|
||||
var p Proxy
|
||||
p.EnabledFlag = "yes"
|
||||
|
@ -341,6 +343,7 @@ func (s *Service) Disabled() bool {
|
|||
// Auth is 'auth_service' section of the config file
|
||||
type Auth struct {
|
||||
Service `yaml:",inline"`
|
||||
|
||||
// DomainName is the name of the CA who manages this cluster
|
||||
DomainName string `yaml:"cluster_name,omitempty"`
|
||||
|
||||
|
@ -357,9 +360,6 @@ type Auth struct {
|
|||
// 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"`
|
||||
|
||||
// StaticTokens are pre-defined host provisioning tokens supplied via config file for
|
||||
// environments where paranoid security is not needed
|
||||
//
|
||||
|
@ -367,7 +367,16 @@ type Auth struct {
|
|||
// for exmple: "auth,proxy,node:MTIzNGlvemRmOWE4MjNoaQo"
|
||||
StaticTokens []StaticToken `yaml:"tokens,omitempty"`
|
||||
|
||||
// Authentication holds authentication configuration information like authentication
|
||||
// type, second factor type, specific connector information, etc.
|
||||
Authentication *AuthenticationConfig `yaml:"authentication,omitempty"`
|
||||
|
||||
// OIDCConnectors is a list of trusted OpenID Connect Identity providers
|
||||
// Deprecated: Use OIDC section in Authentication section instead.
|
||||
OIDCConnectors []OIDCConnector `yaml:"oidc_connectors"`
|
||||
|
||||
// Configuration for "universal 2nd factor"
|
||||
// Deprecated: Use U2F section in Authentication section instead.
|
||||
U2F U2F `yaml:"u2f,omitempty"`
|
||||
}
|
||||
|
||||
|
@ -384,6 +393,74 @@ type TrustedCluster struct {
|
|||
|
||||
type StaticToken string
|
||||
|
||||
// Parse is applied to a string in "role,role,role:token" format. It breaks it
|
||||
// apart into a slice of roles, token and optional error
|
||||
func (t StaticToken) Parse() (roles teleport.Roles, token string, err error) {
|
||||
parts := strings.Split(string(t), ":")
|
||||
if len(parts) != 2 {
|
||||
return nil, "", trace.BadParameter("invalid static token spec: %q", t)
|
||||
}
|
||||
roles, err = teleport.ParseRoles(parts[0])
|
||||
return roles, parts[1], trace.Wrap(err)
|
||||
}
|
||||
|
||||
type AuthenticationConfig struct {
|
||||
Type string `yaml:"type"`
|
||||
SecondFactor string `yaml:"second_factor,omitempty"`
|
||||
U2F *UniversalSecondFactor `yaml:"u2f,omitempty"`
|
||||
OIDC *OIDCConnector `yaml:"oidc,omitempty"`
|
||||
}
|
||||
|
||||
// Parse returns the Authentication Configuration in three parts, the AuthPreference (type and second factor) as well
|
||||
// as OIDCConnector and UniversalSecondFactor that define how those two are configured (if provided).
|
||||
func (a *AuthenticationConfig) Parse() (services.AuthPreference, services.OIDCConnector, services.UniversalSecondFactor, error) {
|
||||
var err error
|
||||
|
||||
ap, err := services.NewAuthPreference(services.AuthPreferenceSpecV2{
|
||||
Type: a.Type,
|
||||
SecondFactor: a.SecondFactor,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
// check to make sure the configuration is valid
|
||||
err = ap.Check()
|
||||
if err != nil {
|
||||
return nil, nil, nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
var oidcConnector services.OIDCConnector
|
||||
if a.OIDC != nil {
|
||||
oidcConnector, err = a.OIDC.Parse()
|
||||
if err != nil {
|
||||
return nil, nil, nil, trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
var universalSecondFactor services.UniversalSecondFactor
|
||||
if a.U2F != nil {
|
||||
universalSecondFactor, err = a.U2F.Parse()
|
||||
if err != nil {
|
||||
return nil, nil, nil, trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
return ap, oidcConnector, universalSecondFactor, nil
|
||||
}
|
||||
|
||||
type UniversalSecondFactor struct {
|
||||
AppID string `yaml:"app_id"`
|
||||
Facets []string `yaml:"facets"`
|
||||
}
|
||||
|
||||
func (u *UniversalSecondFactor) Parse() (services.UniversalSecondFactor, error) {
|
||||
return services.NewUniversalSecondFactor(services.UniversalSecondFactorSpecV2{
|
||||
AppID: u.AppID,
|
||||
Facets: u.Facets,
|
||||
})
|
||||
}
|
||||
|
||||
// SSH is 'ssh_service' section of the config file
|
||||
type SSH struct {
|
||||
Service `yaml:",inline"`
|
||||
|
@ -597,17 +674,6 @@ func (o *OIDCConnector) Parse() (services.OIDCConnector, error) {
|
|||
return v2, nil
|
||||
}
|
||||
|
||||
// Parse() is applied to a string in "role,role,role:token" format. It breaks it
|
||||
// apart into a slice of roles, token and optional error
|
||||
func (t StaticToken) Parse() (roles teleport.Roles, token string, err error) {
|
||||
parts := strings.Split(string(t), ":")
|
||||
if len(parts) != 2 {
|
||||
return nil, "", trace.Errorf("invalid static token spec: '%s'", t)
|
||||
}
|
||||
roles, err = teleport.ParseRoles(parts[0])
|
||||
return roles, parts[1], trace.Wrap(err)
|
||||
}
|
||||
|
||||
type U2F struct {
|
||||
EnabledFlag string `yaml:"enabled"`
|
||||
AppID string `yaml:"app_id,omitempty"`
|
||||
|
|
177
lib/config/fileconf_test.go
Normal file
177
lib/config/fileconf_test.go
Normal file
|
@ -0,0 +1,177 @@
|
|||
/*
|
||||
Copyright 2015 Gravitational, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
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 config
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type FileTestSuite struct {
|
||||
}
|
||||
|
||||
var _ = check.Suite(&FileTestSuite{})
|
||||
var _ = fmt.Printf
|
||||
|
||||
func (s *FileTestSuite) SetUpSuite(c *check.C) {
|
||||
}
|
||||
|
||||
func (s *FileTestSuite) TearDownSuite(c *check.C) {
|
||||
}
|
||||
|
||||
func (s *FileTestSuite) SetUpTest(c *check.C) {
|
||||
}
|
||||
|
||||
func (s *FileTestSuite) TestAuthenticationSection(c *check.C) {
|
||||
tests := []struct {
|
||||
inConfigString string
|
||||
outAuthenticationConfig *AuthenticationConfig
|
||||
}{
|
||||
// 0 - local with otp
|
||||
{
|
||||
`
|
||||
auth_service:
|
||||
authentication:
|
||||
type: local
|
||||
second_factor: otp
|
||||
`,
|
||||
&AuthenticationConfig{
|
||||
Type: "local",
|
||||
SecondFactor: "otp",
|
||||
},
|
||||
},
|
||||
// 1 - local auth without otp
|
||||
{
|
||||
`
|
||||
auth_service:
|
||||
authentication:
|
||||
type: local
|
||||
second_factor: off
|
||||
`,
|
||||
&AuthenticationConfig{
|
||||
Type: "local",
|
||||
SecondFactor: "off",
|
||||
},
|
||||
},
|
||||
// 2 - local auth with u2f
|
||||
{
|
||||
`
|
||||
auth_service:
|
||||
authentication:
|
||||
type: local
|
||||
second_factor: u2f
|
||||
u2f:
|
||||
app_id: https://graviton:3080
|
||||
facets:
|
||||
- https://graviton:3080
|
||||
`,
|
||||
&AuthenticationConfig{
|
||||
Type: "local",
|
||||
SecondFactor: "u2f",
|
||||
U2F: &UniversalSecondFactor{
|
||||
AppID: "https://graviton:3080",
|
||||
Facets: []string{
|
||||
"https://graviton:3080",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// 3 - oidc without second factor
|
||||
{
|
||||
`
|
||||
auth_service:
|
||||
authentication:
|
||||
type: oidc
|
||||
oidc:
|
||||
id: google
|
||||
redirect_url: "https://localhost:3080/v1/webapi/oidc/callback"
|
||||
client_id: id-from-google.apps.googleusercontent.com
|
||||
client_secret: secret-key-from-google
|
||||
issuer_url: "https://accounts.google.com"
|
||||
display: whaterver
|
||||
scope: [ "ssh_permissions", "roles"]
|
||||
claims_to_roles:
|
||||
- claim: role
|
||||
value: admin
|
||||
roles: ["dba", "backup", "root"]
|
||||
`,
|
||||
&AuthenticationConfig{
|
||||
Type: "oidc",
|
||||
OIDC: &OIDCConnector{
|
||||
ID: "google",
|
||||
RedirectURL: "https://localhost:3080/v1/webapi/oidc/callback",
|
||||
ClientID: "id-from-google.apps.googleusercontent.com",
|
||||
ClientSecret: "secret-key-from-google",
|
||||
IssuerURL: "https://accounts.google.com",
|
||||
Display: "whaterver",
|
||||
Scope: []string{
|
||||
"ssh_permissions",
|
||||
"roles",
|
||||
},
|
||||
ClaimsToRoles: []ClaimMapping{
|
||||
ClaimMapping{
|
||||
Claim: "role",
|
||||
Value: "admin",
|
||||
Roles: []string{
|
||||
"dba",
|
||||
"backup",
|
||||
"root",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// run tests
|
||||
for i, tt := range tests {
|
||||
comment := check.Commentf("Test %v", i)
|
||||
|
||||
encodedConfigString := base64.StdEncoding.EncodeToString([]byte(tt.inConfigString))
|
||||
|
||||
fc, err := ReadFromString(encodedConfigString)
|
||||
c.Assert(err, check.IsNil, comment)
|
||||
|
||||
c.Assert(fc.Auth.Authentication, check.DeepEquals, tt.outAuthenticationConfig, comment)
|
||||
}
|
||||
}
|
||||
|
||||
// TestLegacySection ensures we continue to parse and correctly load deprecated
|
||||
// OIDC connector and U2F authentication configuration.
|
||||
func (s *FileTestSuite) TestLegacyAuthenticationSection(c *check.C) {
|
||||
encodedLegacyAuthenticationSection := base64.StdEncoding.EncodeToString([]byte(LegacyAuthenticationSection))
|
||||
|
||||
// read config into struct
|
||||
fc, err := ReadFromString(encodedLegacyAuthenticationSection)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
// validate oidc connector
|
||||
c.Assert(fc.Auth.OIDCConnectors, check.HasLen, 1)
|
||||
c.Assert(fc.Auth.OIDCConnectors[0].ID, check.Equals, "google")
|
||||
c.Assert(fc.Auth.OIDCConnectors[0].RedirectURL, check.Equals, "https://localhost:3080/v1/webapi/oidc/callback")
|
||||
c.Assert(fc.Auth.OIDCConnectors[0].ClientID, check.Equals, "id-from-google.apps.googleusercontent.com")
|
||||
c.Assert(fc.Auth.OIDCConnectors[0].ClientSecret, check.Equals, "secret-key-from-google")
|
||||
c.Assert(fc.Auth.OIDCConnectors[0].IssuerURL, check.Equals, "https://accounts.google.com")
|
||||
|
||||
// validate u2f
|
||||
c.Assert(fc.Auth.U2F.EnabledFlag, check.Equals, "yes")
|
||||
c.Assert(fc.Auth.U2F.AppID, check.Equals, "https://graviton:3080")
|
||||
c.Assert(fc.Auth.U2F.Facets, check.HasLen, 1)
|
||||
c.Assert(fc.Auth.U2F.Facets[0], check.Equals, "https://graviton:3080")
|
||||
}
|
146
lib/config/testdata_test.go
Normal file
146
lib/config/testdata_test.go
Normal file
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
Copyright 2015 Gravitational, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
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 config
|
||||
|
||||
const StaticConfigString = `
|
||||
#
|
||||
# Some comments
|
||||
#
|
||||
teleport:
|
||||
nodename: edsger.example.com
|
||||
advertise_ip: 10.10.10.1
|
||||
pid_file: /var/run/teleport.pid
|
||||
auth_servers:
|
||||
- auth0.server.example.org:3024
|
||||
- auth1.server.example.org:3024
|
||||
auth_token: xxxyyy
|
||||
log:
|
||||
output: stderr
|
||||
severity: INFO
|
||||
storage:
|
||||
type: etcd
|
||||
peers: ['one', 'two']
|
||||
tls_key_file: /tls.key
|
||||
tls_cert_file: /tls.cert
|
||||
tls_ca_file: /tls.ca
|
||||
connection_limits:
|
||||
max_connections: 90
|
||||
max_users: 91
|
||||
rates:
|
||||
- period: 1m1s
|
||||
average: 70
|
||||
burst: 71
|
||||
- period: 10m10s
|
||||
average: 170
|
||||
burst: 171
|
||||
keys:
|
||||
- cert: node.cert
|
||||
private_key: !!binary cHJpdmF0ZSBrZXk=
|
||||
- cert_file: /proxy.cert.file
|
||||
private_key_file: /proxy.key.file
|
||||
|
||||
auth_service:
|
||||
enabled: yes
|
||||
listen_addr: auth:3025
|
||||
tokens:
|
||||
- "proxy,node:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
- "auth:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
authorities:
|
||||
- type: host
|
||||
domain_name: example.com
|
||||
checking_keys:
|
||||
- checking key 1
|
||||
checking_key_files:
|
||||
- /ca.checking.key
|
||||
signing_keys:
|
||||
- !!binary c2lnbmluZyBrZXkgMQ==
|
||||
signing_key_files:
|
||||
- /ca.signing.key
|
||||
reverse_tunnels:
|
||||
- domain_name: tunnel.example.com
|
||||
addresses: ["com-1", "com-2"]
|
||||
- domain_name: tunnel.example.org
|
||||
addresses: ["org-1"]
|
||||
|
||||
ssh_service:
|
||||
enabled: no
|
||||
listen_addr: ssh:3025
|
||||
labels:
|
||||
name: mongoserver
|
||||
role: slave
|
||||
commands:
|
||||
- name: hostname
|
||||
command: [/bin/hostname]
|
||||
period: 10ms
|
||||
- name: date
|
||||
command: [/bin/date]
|
||||
period: 20ms
|
||||
`
|
||||
|
||||
const SmallConfigString = `
|
||||
teleport:
|
||||
nodename: cat.example.com
|
||||
advertise_ip: 10.10.10.1
|
||||
pid_file: /var/run/teleport.pid
|
||||
auth_servers:
|
||||
- auth0.server.example.org:3024
|
||||
- auth1.server.example.org:3024
|
||||
auth_token: xxxyyy
|
||||
log:
|
||||
output: stderr
|
||||
severity: INFO
|
||||
connection_limits:
|
||||
max_connections: 90
|
||||
max_users: 91
|
||||
rates:
|
||||
- period: 1m1s
|
||||
average: 70
|
||||
burst: 71
|
||||
- period: 10m10s
|
||||
average: 170
|
||||
burst: 171
|
||||
auth_service:
|
||||
enabled: yes
|
||||
listen_addr: 10.5.5.1:3025
|
||||
cluster_name: magadan
|
||||
tokens:
|
||||
- "proxy,node:xxx"
|
||||
- "auth:yyy"
|
||||
ssh_service:
|
||||
enabled: no
|
||||
|
||||
proxy_service:
|
||||
enabled: yes
|
||||
web_listen_addr: webhost
|
||||
tunnel_listen_addr: tunnelhost:1001
|
||||
`
|
||||
|
||||
// LegacyAuthenticationSection is the deprecated format for authentication method. We still
|
||||
// need to support it until it's fully removed.
|
||||
const LegacyAuthenticationSection = `
|
||||
auth_service:
|
||||
oidc_connectors:
|
||||
- id: google
|
||||
redirect_url: https://localhost:3080/v1/webapi/oidc/callback
|
||||
client_id: id-from-google.apps.googleusercontent.com
|
||||
client_secret: secret-key-from-google
|
||||
issuer_url: https://accounts.google.com
|
||||
u2f:
|
||||
enabled: "yes"
|
||||
app_id: https://graviton:3080
|
||||
facets:
|
||||
- https://graviton:3080
|
||||
`
|
|
@ -17,11 +17,9 @@ limitations under the License.
|
|||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/gravitational/teleport/lib/auth"
|
||||
"github.com/gravitational/teleport/lib/backend"
|
||||
|
@ -209,7 +207,12 @@ type AuthConfig struct {
|
|||
// NoAudit, when set to true, disables session recording and event audit
|
||||
NoAudit bool
|
||||
|
||||
U2F services.U2F
|
||||
// Preference defines the authentication preference (type and second factor) for
|
||||
// the auth server.
|
||||
Preference services.AuthPreference
|
||||
|
||||
// U2F defines is settings for Universal Second Factor (appID and facets).
|
||||
U2F services.UniversalSecondFactor
|
||||
}
|
||||
|
||||
// SSHConfig configures SSH server node role
|
||||
|
@ -247,9 +250,6 @@ func ApplyDefaults(cfg *Config) {
|
|||
// defaults for the auth service:
|
||||
cfg.Auth.Enabled = true
|
||||
cfg.Auth.SSHAddr = *defaults.AuthListenAddr()
|
||||
cfg.Auth.U2F.Enabled = false
|
||||
cfg.Auth.U2F.AppID = fmt.Sprintf("https://%s:%d", strings.ToLower(hostname), defaults.HTTPListenPort)
|
||||
cfg.Auth.U2F.Facets = []string{cfg.Auth.U2F.AppID}
|
||||
cfg.Auth.StorageConfig.Type = boltbk.GetName()
|
||||
cfg.Auth.StorageConfig.Params = backend.Params{"path": cfg.DataDir}
|
||||
defaults.ConfigureLimiter(&cfg.Auth.Limiter)
|
||||
|
|
|
@ -324,15 +324,16 @@ func (process *TeleportProcess) initAuthService(authority auth.Authority) error
|
|||
NodeName: cfg.Hostname,
|
||||
Authorities: cfg.Auth.Authorities,
|
||||
ReverseTunnels: cfg.ReverseTunnels,
|
||||
OIDCConnectors: cfg.OIDCConnectors,
|
||||
Trust: cfg.Trust,
|
||||
Presence: cfg.Presence,
|
||||
Provisioner: cfg.Provisioner,
|
||||
Identity: cfg.Identity,
|
||||
Access: cfg.Access,
|
||||
StaticTokens: cfg.Auth.StaticTokens,
|
||||
U2F: cfg.Auth.U2F,
|
||||
Roles: cfg.Auth.Roles,
|
||||
AuthPreference: cfg.Auth.Preference,
|
||||
OIDCConnectors: cfg.OIDCConnectors,
|
||||
U2F: cfg.Auth.U2F,
|
||||
}, cfg.SeedConfig)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
|
|
197
lib/services/authentication.go
Normal file
197
lib/services/authentication.go
Normal file
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
Copyright 2017 Gravitational, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
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 (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/gravitational/teleport"
|
||||
"github.com/gravitational/teleport/lib/defaults"
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
)
|
||||
|
||||
// ClusterAuthPreference defines an interface to get and set
|
||||
// authentication preferences for a cluster.
|
||||
type ClusterAuthPreference interface {
|
||||
// GetClusterAuthPreference returns the authentication preferences for a cluster.
|
||||
GetClusterAuthPreference() (AuthPreference, error)
|
||||
|
||||
// SetClusterAuthPreference sets the authentication preferences for a cluster.
|
||||
SetClusterAuthPreference(AuthPreference) error
|
||||
}
|
||||
|
||||
// AuthPreference defines the authentication preferences for a specific
|
||||
// cluster. It defines the type (local, oidc) and second factor (off, otp, oidc).
|
||||
type AuthPreference interface {
|
||||
// GetType returns the type of authentication.
|
||||
GetType() string
|
||||
|
||||
// SetType sets the type of authentication.
|
||||
SetType(string)
|
||||
|
||||
// GetSecondFactor returns the type of second factor.
|
||||
GetSecondFactor() string
|
||||
|
||||
// SetSecondFactor sets the type of second factor.
|
||||
SetSecondFactor(string)
|
||||
|
||||
// Check verifies the constraints for AuthPreference.
|
||||
Check() error
|
||||
}
|
||||
|
||||
// NewAuthPreference is a convenience method to to create AuthPreferenceV2.
|
||||
func NewAuthPreference(spec AuthPreferenceSpecV2) (AuthPreference, error) {
|
||||
return &AuthPreferenceV2{
|
||||
Kind: KindClusterAuthPreference,
|
||||
Version: V2,
|
||||
Metadata: Metadata{
|
||||
Name: MetaNameClusterAuthPreference,
|
||||
Namespace: defaults.Namespace,
|
||||
},
|
||||
Spec: spec,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// AuthPreferenceV2 implements AuthPreference.
|
||||
type AuthPreferenceV2 struct {
|
||||
// Kind is a resource kind - always resource.
|
||||
Kind string `json:"kind"`
|
||||
|
||||
// Version is a resource version.
|
||||
Version string `json:"version"`
|
||||
|
||||
// Metadata is metadata about the resource.
|
||||
Metadata Metadata `json:"metadata"`
|
||||
|
||||
// Spec is the specification of the resource.
|
||||
Spec AuthPreferenceSpecV2 `json:"spec"`
|
||||
}
|
||||
|
||||
// AuthPreferenceSpecV2 is the actual data we care about for AuthPreferenceV2.
|
||||
type AuthPreferenceSpecV2 struct {
|
||||
// Type is the type of authentication.
|
||||
Type string `json:"type"`
|
||||
|
||||
// SecondFactor is the type of second factor.
|
||||
SecondFactor string `json:"second_factor"`
|
||||
}
|
||||
|
||||
// GetType returns the type of authentication.
|
||||
func (c *AuthPreferenceV2) GetType() string {
|
||||
return c.Spec.Type
|
||||
}
|
||||
|
||||
// SetType sets the type of authentication.
|
||||
func (c *AuthPreferenceV2) SetType(s string) {
|
||||
c.Spec.Type = s
|
||||
}
|
||||
|
||||
// GetSecondFactor returns the type of second factor.
|
||||
func (c *AuthPreferenceV2) GetSecondFactor() string {
|
||||
return c.Spec.SecondFactor
|
||||
}
|
||||
|
||||
// SetSecondFactor sets the type of second factor.
|
||||
func (c *AuthPreferenceV2) SetSecondFactor(s string) {
|
||||
c.Spec.SecondFactor = s
|
||||
}
|
||||
|
||||
// Check verifies the constraints for AuthPreference.
|
||||
func (c *AuthPreferenceV2) Check() error {
|
||||
switch c.Spec.Type {
|
||||
case teleport.Local:
|
||||
if c.Spec.SecondFactor != teleport.OFF && c.Spec.SecondFactor != teleport.OTP && c.Spec.SecondFactor != teleport.U2F {
|
||||
return trace.BadParameter("second factor type %q not supported", c.Spec.SecondFactor)
|
||||
}
|
||||
case teleport.OIDC:
|
||||
if c.Spec.SecondFactor != "" {
|
||||
return trace.BadParameter("second factor not supported with oidc connector")
|
||||
}
|
||||
default:
|
||||
return trace.BadParameter("unsupported type %q", c.Spec.Type)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const AuthPreferenceSpecSchemaTemplate = `{
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"type": {"type": "string"},
|
||||
"second_factor": {"type": "string"}%v
|
||||
}
|
||||
}`
|
||||
|
||||
// GetAuthPreferenceSchema returns the schema with optionally injected
|
||||
// schema for extensions.
|
||||
func GetAuthPreferenceSchema(extensionSchema string) string {
|
||||
var authPreferenceSchema string
|
||||
if authPreferenceSchema == "" {
|
||||
authPreferenceSchema = fmt.Sprintf(AuthPreferenceSpecSchemaTemplate, "")
|
||||
} else {
|
||||
authPreferenceSchema = fmt.Sprintf(AuthPreferenceSpecSchemaTemplate, ","+extensionSchema)
|
||||
}
|
||||
return fmt.Sprintf(V2SchemaTemplate, MetadataSchema, authPreferenceSchema)
|
||||
}
|
||||
|
||||
// AuthPreferenceMarshaler implements marshal/unmarshal of AuthPreference implementations
|
||||
// mostly adds support for extended versions.
|
||||
type AuthPreferenceMarshaler interface {
|
||||
Marshal(c AuthPreference, opts ...MarshalOption) ([]byte, error)
|
||||
Unmarshal(bytes []byte) (AuthPreference, error)
|
||||
}
|
||||
|
||||
var authPreferenceMarshaler AuthPreferenceMarshaler = &TeleportAuthPreferenceMarshaler{}
|
||||
|
||||
func SetAuthPreferenceMarshaler(m AuthPreferenceMarshaler) {
|
||||
marshalerMutex.Lock()
|
||||
defer marshalerMutex.Unlock()
|
||||
authPreferenceMarshaler = m
|
||||
}
|
||||
|
||||
func GetAuthPreferenceMarshaler() AuthPreferenceMarshaler {
|
||||
marshalerMutex.Lock()
|
||||
defer marshalerMutex.Unlock()
|
||||
return authPreferenceMarshaler
|
||||
}
|
||||
|
||||
type TeleportAuthPreferenceMarshaler struct{}
|
||||
|
||||
// Unmarshal unmarshals role from JSON or YAML.
|
||||
func (t *TeleportAuthPreferenceMarshaler) Unmarshal(bytes []byte) (AuthPreference, error) {
|
||||
var authPreference AuthPreferenceV2
|
||||
|
||||
if len(bytes) == 0 {
|
||||
return nil, trace.BadParameter("missing resource data")
|
||||
}
|
||||
|
||||
err := utils.UnmarshalWithSchema(GetAuthPreferenceSchema(""), &authPreference, bytes)
|
||||
if err != nil {
|
||||
return nil, trace.BadParameter(err.Error())
|
||||
}
|
||||
|
||||
return &authPreference, nil
|
||||
}
|
||||
|
||||
// Marshal marshals role to JSON or YAML.
|
||||
func (t *TeleportAuthPreferenceMarshaler) Marshal(c AuthPreference, opts ...MarshalOption) ([]byte, error) {
|
||||
return json.Marshal(c)
|
||||
}
|
67
lib/services/authentication_test.go
Normal file
67
lib/services/authentication_test.go
Normal file
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
Copyright 2017 Gravitational, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/gravitational/teleport/lib/defaults"
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
|
||||
"gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type ClusterAuthPreferenceSuite struct{}
|
||||
|
||||
var _ = check.Suite(&ClusterAuthPreferenceSuite{})
|
||||
var _ = fmt.Printf
|
||||
|
||||
func (s *ClusterAuthPreferenceSuite) SetUpSuite(c *check.C) {
|
||||
utils.InitLoggerForTests()
|
||||
}
|
||||
|
||||
func (s *ClusterAuthPreferenceSuite) TestUnmarshal(c *check.C) {
|
||||
input := `
|
||||
{
|
||||
"kind": "cluster_auth_preference",
|
||||
"metadata": {
|
||||
"name": "cluster-auth-preference"
|
||||
},
|
||||
"spec": {
|
||||
"type": "local",
|
||||
"second_factor": "otp"
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
output := AuthPreferenceV2{
|
||||
Kind: KindClusterAuthPreference,
|
||||
Version: V2,
|
||||
Metadata: Metadata{
|
||||
Name: MetaNameClusterAuthPreference,
|
||||
Namespace: defaults.Namespace,
|
||||
},
|
||||
Spec: AuthPreferenceSpecV2{
|
||||
Type: "local",
|
||||
SecondFactor: "otp",
|
||||
},
|
||||
}
|
||||
|
||||
ap, err := GetAuthPreferenceMarshaler().Unmarshal([]byte(input))
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(ap, check.DeepEquals, &output)
|
||||
}
|
|
@ -282,6 +282,7 @@ func (i *OIDCAuthRequest) Check() error {
|
|||
}
|
||||
|
||||
// U2F is a configuration of the U2F two factor authentication
|
||||
// Deprecated: Use services.UniversalSecondFactor instead.
|
||||
type U2F struct {
|
||||
Enabled bool
|
||||
// AppID identifies the website to the U2F keys. It should not be changed once a U2F
|
||||
|
|
65
lib/services/local/authentication.go
Normal file
65
lib/services/local/authentication.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
Copyright 2017 Gravitational, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
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 local
|
||||
|
||||
import (
|
||||
"github.com/gravitational/teleport/lib/backend"
|
||||
"github.com/gravitational/teleport/lib/services"
|
||||
"github.com/gravitational/trace"
|
||||
)
|
||||
|
||||
// ClusterAuthPreferenceService is responsible for managing cluster authentication preferences.
|
||||
type ClusterAuthPreferenceService struct {
|
||||
backend.Backend
|
||||
}
|
||||
|
||||
// NewClusterAuthPreferenceService returns a new ClusterAuthPreferenceService.
|
||||
func NewClusterAuthPreferenceService(backend backend.Backend) *ClusterAuthPreferenceService {
|
||||
return &ClusterAuthPreferenceService{
|
||||
Backend: backend,
|
||||
}
|
||||
}
|
||||
|
||||
// GetClusterAuthPreference fetches the cluster authentication preferences
|
||||
// from the backend and return them.
|
||||
func (s *ClusterAuthPreferenceService) GetClusterAuthPreference() (services.AuthPreference, error) {
|
||||
data, err := s.GetVal([]string{"authentication"}, "preference")
|
||||
if err != nil {
|
||||
if trace.IsNotFound(err) {
|
||||
return nil, trace.NotFound("authentication preference not found")
|
||||
}
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
return services.GetAuthPreferenceMarshaler().Unmarshal(data)
|
||||
}
|
||||
|
||||
// SetClusterAuthPreference sets the cluster authentication preferences
|
||||
// on the backend.
|
||||
func (s *ClusterAuthPreferenceService) SetClusterAuthPreference(preferences services.AuthPreference) error {
|
||||
data, err := services.GetAuthPreferenceMarshaler().Marshal(preferences)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
err = s.UpsertVal([]string{"authentication"}, "preference", []byte(data), backend.Forever)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
83
lib/services/local/authentication_test.go
Normal file
83
lib/services/local/authentication_test.go
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
Copyright 2017 Gravitational, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
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 local
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/gravitational/teleport/lib/backend"
|
||||
"github.com/gravitational/teleport/lib/backend/boltbk"
|
||||
"github.com/gravitational/teleport/lib/services"
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
|
||||
"gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type ClusterAuthPreferenceSuite struct {
|
||||
bk backend.Backend
|
||||
tempDir string
|
||||
}
|
||||
|
||||
var _ = check.Suite(&ClusterAuthPreferenceSuite{})
|
||||
var _ = fmt.Printf
|
||||
|
||||
func (s *ClusterAuthPreferenceSuite) SetUpSuite(c *check.C) {
|
||||
utils.InitLoggerForTests()
|
||||
}
|
||||
|
||||
func (s *ClusterAuthPreferenceSuite) TearDownSuite(c *check.C) {
|
||||
}
|
||||
|
||||
func (s *ClusterAuthPreferenceSuite) SetUpTest(c *check.C) {
|
||||
var err error
|
||||
|
||||
s.tempDir, err = ioutil.TempDir("", "preference-test-")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
s.bk, err = boltbk.New(backend.Params{"path": s.tempDir})
|
||||
c.Assert(err, check.IsNil)
|
||||
}
|
||||
|
||||
func (s *ClusterAuthPreferenceSuite) TearDownTest(c *check.C) {
|
||||
var err error
|
||||
|
||||
c.Assert(s.bk.Close(), check.IsNil)
|
||||
|
||||
err = os.RemoveAll(s.tempDir)
|
||||
c.Assert(err, check.IsNil)
|
||||
}
|
||||
|
||||
func (s *ClusterAuthPreferenceSuite) TestCycle(c *check.C) {
|
||||
caps := NewClusterAuthPreferenceService(s.bk)
|
||||
|
||||
ap, err := services.NewAuthPreference(services.AuthPreferenceSpecV2{
|
||||
Type: "local",
|
||||
SecondFactor: "otp",
|
||||
})
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
err = caps.SetClusterAuthPreference(ap)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
gotAP, err := caps.GetClusterAuthPreference()
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
c.Assert(gotAP.GetType(), check.Equals, "local")
|
||||
c.Assert(gotAP.GetSecondFactor(), check.Equals, "otp")
|
||||
}
|
65
lib/services/local/u2f.go
Normal file
65
lib/services/local/u2f.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
Copyright 2017 Gravitational, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
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 local
|
||||
|
||||
import (
|
||||
"github.com/gravitational/teleport/lib/backend"
|
||||
"github.com/gravitational/teleport/lib/services"
|
||||
"github.com/gravitational/trace"
|
||||
)
|
||||
|
||||
// UniversalSecondFactorService is responsible for managing universal second factor settings.
|
||||
type UniversalSecondFactorService struct {
|
||||
backend.Backend
|
||||
}
|
||||
|
||||
// NewUniversalSecondFactorService returns a new UniversalSecondFactorService.
|
||||
func NewUniversalSecondFactorService(backend backend.Backend) *UniversalSecondFactorService {
|
||||
return &UniversalSecondFactorService{
|
||||
Backend: backend,
|
||||
}
|
||||
}
|
||||
|
||||
// GetUniversalSecondFactor fetches the universal second factor settings
|
||||
// from the backend and returns them.
|
||||
func (s *UniversalSecondFactorService) GetUniversalSecondFactor() (services.UniversalSecondFactor, error) {
|
||||
data, err := s.GetVal([]string{"authentication", "preference"}, "u2f")
|
||||
if err != nil {
|
||||
if trace.IsNotFound(err) {
|
||||
return nil, trace.NotFound("no universal second factor settings")
|
||||
}
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
return services.GetUniversalSecondFactorMarshaler().Unmarshal(data)
|
||||
}
|
||||
|
||||
// GetUniversalSecondFactor sets the universal second factor settings
|
||||
// on the backend.
|
||||
func (s *UniversalSecondFactorService) SetUniversalSecondFactor(settings services.UniversalSecondFactor) error {
|
||||
data, err := services.GetUniversalSecondFactorMarshaler().Marshal(settings)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
err = s.UpsertVal([]string{"authentication", "preference"}, "u2f", []byte(data), backend.Forever)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2015 Gravitational, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
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 (
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2015 Gravitational, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
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 (
|
||||
|
@ -74,6 +90,18 @@ const (
|
|||
// KindOIDCConnector is a OIDC connector resource
|
||||
KindOIDCConnector = "oidc"
|
||||
|
||||
// KindAuthPreference is the type of authentication for this cluster.
|
||||
KindClusterAuthPreference = "cluster_auth_preference"
|
||||
|
||||
// KindAuthPreference is the type of authentication for this cluster.
|
||||
MetaNameClusterAuthPreference = "cluster-auth-preference"
|
||||
|
||||
// KindUniversalSecondFactor is a type of second factor authentication.
|
||||
KindUniversalSecondFactor = "universal_second_factor"
|
||||
|
||||
// MetaNameUniversalSecondFactor is a type of second factor authentication.
|
||||
MetaNameUniversalSecondFactor = "universal-second-factor"
|
||||
|
||||
// V2 is our current version
|
||||
V2 = "v2"
|
||||
|
||||
|
|
179
lib/services/u2f.go
Normal file
179
lib/services/u2f.go
Normal file
|
@ -0,0 +1,179 @@
|
|||
/*
|
||||
Copyright 2017 Gravitational, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
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 (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/gravitational/teleport/lib/defaults"
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
"github.com/gravitational/trace"
|
||||
)
|
||||
|
||||
// UniversalSecondFactorSettings defines the interface to get and set
|
||||
// Universal Second Factor settings.
|
||||
type UniversalSecondFactorSettings interface {
|
||||
// GetUniversalSecondFactor returns universal second factor settings.
|
||||
GetUniversalSecondFactor() (UniversalSecondFactor, error)
|
||||
|
||||
// SetUniversalSecondFactor sets universal second factor settings.
|
||||
SetUniversalSecondFactor(UniversalSecondFactor) error
|
||||
}
|
||||
|
||||
// UniversalSecondFactor defines settings for Universal Second Factor
|
||||
// like the AppID and Facets.
|
||||
type UniversalSecondFactor interface {
|
||||
// GetAppID returns the application ID for universal second factor.
|
||||
GetAppID() string
|
||||
|
||||
// SetAppID sets the application ID for universal second factor.
|
||||
SetAppID(string)
|
||||
|
||||
// GetFacets returns the facets for universal second factor.
|
||||
GetFacets() []string
|
||||
|
||||
// SetFacets sets the facets for universal second factor.
|
||||
SetFacets([]string)
|
||||
}
|
||||
|
||||
// NewUniversalSecondFactor is a convenience method to to create UniversalSecondFactorV2.
|
||||
func NewUniversalSecondFactor(spec UniversalSecondFactorSpecV2) (UniversalSecondFactor, error) {
|
||||
return &UniversalSecondFactorV2{
|
||||
Kind: KindUniversalSecondFactor,
|
||||
Version: V2,
|
||||
Metadata: Metadata{
|
||||
Name: MetaNameUniversalSecondFactor,
|
||||
Namespace: defaults.Namespace,
|
||||
},
|
||||
Spec: spec,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UniversalSecondFactorV2 implements UniversalSecondFactor.
|
||||
type UniversalSecondFactorV2 struct {
|
||||
// Kind is a resource kind - always resource.
|
||||
Kind string `json:"kind"`
|
||||
|
||||
// Version is a resource version.
|
||||
Version string `json:"version"`
|
||||
|
||||
// Metadata is metadata about the resource.
|
||||
Metadata Metadata `json:"metadata"`
|
||||
|
||||
// Spec is the specification of the resource.
|
||||
Spec UniversalSecondFactorSpecV2 `json:"spec"`
|
||||
}
|
||||
|
||||
// UniversalSecondFactorSpecV2 is the actual data we care about for UniversalSecondFactorV2.
|
||||
type UniversalSecondFactorSpecV2 struct {
|
||||
// AppID is the application ID for universal second factor.
|
||||
AppID string `json:"app_id"`
|
||||
|
||||
// Facets are the facets for universal second factor.
|
||||
Facets []string `json:"facets"`
|
||||
}
|
||||
|
||||
// GetAppID returns the application ID for universal second factor.
|
||||
func (c *UniversalSecondFactorV2) GetAppID() string {
|
||||
return c.Spec.AppID
|
||||
}
|
||||
|
||||
// SetAppID sets the application ID for universal second factor.
|
||||
func (c *UniversalSecondFactorV2) SetAppID(s string) {
|
||||
c.Spec.AppID = s
|
||||
}
|
||||
|
||||
// GetFacets returns the facets for universal second factor.
|
||||
func (c *UniversalSecondFactorV2) GetFacets() []string {
|
||||
return c.Spec.Facets
|
||||
}
|
||||
|
||||
// SetFacets sets the facets for universal second factor.
|
||||
func (c *UniversalSecondFactorV2) SetFacets(s []string) {
|
||||
c.Spec.Facets = s
|
||||
}
|
||||
|
||||
const UniversalSecondFactorSpecSchemaTemplate = `{
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"app_id": {"type": "string"},
|
||||
"facets": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}%v
|
||||
}
|
||||
}`
|
||||
|
||||
// GetUniversalSecondFactorSchema returns the schema with optionally injected
|
||||
// schema for extensions.
|
||||
func GetUniversalSecondFactorSchema(extensionSchema string) string {
|
||||
var authPreferenceSchema string
|
||||
if authPreferenceSchema == "" {
|
||||
authPreferenceSchema = fmt.Sprintf(UniversalSecondFactorSpecSchemaTemplate, "")
|
||||
} else {
|
||||
authPreferenceSchema = fmt.Sprintf(UniversalSecondFactorSpecSchemaTemplate, ","+extensionSchema)
|
||||
}
|
||||
return fmt.Sprintf(V2SchemaTemplate, MetadataSchema, authPreferenceSchema)
|
||||
}
|
||||
|
||||
// UniversalSecondFactorMarshaler implements marshal/unmarshal of UniversalSecondFactor implementations
|
||||
// mostly adds support for extended versions.
|
||||
type UniversalSecondFactorMarshaler interface {
|
||||
Marshal(c UniversalSecondFactor, opts ...MarshalOption) ([]byte, error)
|
||||
Unmarshal(bytes []byte) (UniversalSecondFactor, error)
|
||||
}
|
||||
|
||||
var universalSecondFactorMarshaler UniversalSecondFactorMarshaler = &TeleportUniversalSecondFactorMarshaler{}
|
||||
|
||||
func SetUniversalSecondFactorMarshaler(m UniversalSecondFactorMarshaler) {
|
||||
marshalerMutex.Lock()
|
||||
defer marshalerMutex.Unlock()
|
||||
universalSecondFactorMarshaler = m
|
||||
}
|
||||
|
||||
func GetUniversalSecondFactorMarshaler() UniversalSecondFactorMarshaler {
|
||||
marshalerMutex.Lock()
|
||||
defer marshalerMutex.Unlock()
|
||||
return universalSecondFactorMarshaler
|
||||
}
|
||||
|
||||
type TeleportUniversalSecondFactorMarshaler struct{}
|
||||
|
||||
// Unmarshal unmarshals role from JSON or YAML.
|
||||
func (t *TeleportUniversalSecondFactorMarshaler) Unmarshal(bytes []byte) (UniversalSecondFactor, error) {
|
||||
var authPreference UniversalSecondFactorV2
|
||||
|
||||
if len(bytes) == 0 {
|
||||
return nil, trace.BadParameter("missing resource data")
|
||||
}
|
||||
|
||||
err := utils.UnmarshalWithSchema(GetUniversalSecondFactorSchema(""), &authPreference, bytes)
|
||||
if err != nil {
|
||||
return nil, trace.BadParameter(err.Error())
|
||||
}
|
||||
|
||||
return &authPreference, nil
|
||||
}
|
||||
|
||||
// Marshal marshals role to JSON or YAML.
|
||||
func (t *TeleportUniversalSecondFactorMarshaler) Marshal(c UniversalSecondFactor, opts ...MarshalOption) ([]byte, error) {
|
||||
return json.Marshal(c)
|
||||
}
|
|
@ -312,11 +312,15 @@ func (m *Handler) getSettings(w http.ResponseWriter, r *http.Request) (interface
|
|||
if len(settings.Auth.OIDCConnectors) == 0 {
|
||||
settings.Auth.OIDCConnectors = make([]oidcConnector, 0)
|
||||
}
|
||||
u2fAppID, err := m.cfg.ProxyClient.GetU2FAppID()
|
||||
|
||||
universalSecondFactor, err := m.cfg.ProxyClient.GetUniversalSecondFactor()
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
if err != nil {
|
||||
settings.Auth.U2FAppID = ""
|
||||
} else {
|
||||
settings.Auth.U2FAppID = u2fAppID
|
||||
settings.Auth.U2FAppID = universalSecondFactor.GetAppID()
|
||||
}
|
||||
out, err := json.Marshal(settings)
|
||||
if err != nil {
|
||||
|
|
|
@ -145,15 +145,19 @@ func (s *WebSuite) SetUpTest(c *C) {
|
|||
Backend: s.bk,
|
||||
Authority: authority.New(),
|
||||
DomainName: s.domainName,
|
||||
U2F: services.U2F{
|
||||
Enabled: true,
|
||||
AppID: "https://" + s.domainName,
|
||||
Facets: []string{"https://" + s.domainName},
|
||||
},
|
||||
Identity: identity,
|
||||
Access: access,
|
||||
Identity: identity,
|
||||
Access: access,
|
||||
})
|
||||
|
||||
// configure u2f
|
||||
universalSecondFactor, err := services.NewUniversalSecondFactor(services.UniversalSecondFactorSpecV2{
|
||||
AppID: "https://" + s.domainName,
|
||||
Facets: []string{"https://" + s.domainName},
|
||||
})
|
||||
c.Assert(err, IsNil)
|
||||
err = authServer.SetUniversalSecondFactor(universalSecondFactor)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
teleUser, err := services.NewUser(s.user)
|
||||
c.Assert(err, IsNil)
|
||||
role := services.RoleForUser(teleUser)
|
||||
|
|
Loading…
Reference in a new issue