From 4877c78312f9441d71e1bab4edf34b44e92bfa6b Mon Sep 17 00:00:00 2001 From: Guy Lewin Date: Sat, 22 Feb 2020 11:14:00 -0500 Subject: [PATCH] feat: authenticate NewWorkConn messages, similar to ping --- client/control.go | 4 ++ models/auth/auth.go | 29 +++++++++--- models/auth/oidc.go | 59 ++++++++++++++++-------- models/auth/token.go | 27 +++++++---- models/config/client_common.go | 72 ++++++++++++++++------------- models/config/server_common.go | 82 +++++++++++++++++++--------------- models/msg/msg.go | 3 +- server/service.go | 5 +++ 8 files changed, 180 insertions(+), 101 deletions(-) diff --git a/client/control.go b/client/control.go index 441ae68a..84f7af2b 100644 --- a/client/control.go +++ b/client/control.go @@ -142,6 +142,10 @@ func (ctl *Control) HandleReqWorkConn(inMsg *msg.ReqWorkConn) { m := &msg.NewWorkConn{ RunId: ctl.runId, } + if err = ctl.authSetter.SetNewWorkConn(m); err != nil { + xl.Warn("error during NewWorkConn authentication: %v", err) + return + } if err = msg.WriteMsg(workConn, m); err != nil { xl.Warn("work connection write to server error: %v", err) workConn.Close() diff --git a/models/auth/auth.go b/models/auth/auth.go index 3b809b79..f7308c5e 100644 --- a/models/auth/auth.go +++ b/models/auth/auth.go @@ -20,46 +20,63 @@ import ( "github.com/fatedier/frp/models/msg" ) +type BaseAuth struct { + authenticateHeartBeats bool + authenticateNewWorkConns bool +} + type Setter interface { SetLogin(*msg.Login) error SetPing(*msg.Ping) error + SetNewWorkConn(*msg.NewWorkConn) error } func NewAuthSetter(cfg config.ClientCommonConf) (authProvider Setter) { + baseAuth := BaseAuth{ + authenticateHeartBeats: cfg.AuthenticateHeartBeats, + authenticateNewWorkConns: cfg.AuthenticateNewWorkConns, + } + switch cfg.AuthenticationMethod { case consts.TokenAuthMethod: - authProvider = NewTokenAuth(cfg.Token) + authProvider = NewTokenAuth(baseAuth, cfg.Token) case consts.OidcAuthMethod: authProvider = NewOidcAuthSetter( + baseAuth, cfg.OidcClientId, cfg.OidcClientSecret, cfg.OidcAudience, cfg.OidcTokenEndpointUrl, - cfg.AuthenticateHeartBeats, ) } - return + return authProvider } type Verifier interface { VerifyLogin(*msg.Login) error VerifyPing(*msg.Ping) error + VerifyNewWorkConn(*msg.NewWorkConn) error } func NewAuthVerifier(cfg config.ServerCommonConf) (authVerifier Verifier) { + baseAuth := BaseAuth{ + authenticateHeartBeats: cfg.AuthenticateHeartBeats, + authenticateNewWorkConns: cfg.AuthenticateNewWorkConns, + } + switch cfg.AuthenticationMethod { case consts.TokenAuthMethod: - authVerifier = NewTokenAuth(cfg.Token) + authVerifier = NewTokenAuth(baseAuth, cfg.Token) case consts.OidcAuthMethod: authVerifier = NewOidcAuthVerifier( + baseAuth, cfg.OidcIssuer, cfg.OidcAudience, cfg.OidcSkipExpiryCheck, cfg.OidcSkipIssuerCheck, - cfg.AuthenticateHeartBeats, ) } - return + return authVerifier } diff --git a/models/auth/oidc.go b/models/auth/oidc.go index fb68a96e..651fe8f1 100644 --- a/models/auth/oidc.go +++ b/models/auth/oidc.go @@ -25,11 +25,12 @@ import ( ) type OidcAuthProvider struct { - tokenGenerator *clientcredentials.Config - authenticateHeartBeats bool + BaseAuth + + tokenGenerator *clientcredentials.Config } -func NewOidcAuthSetter(clientId string, clientSecret string, audience string, tokenEndpointUrl string, authenticateHeartBeats bool) *OidcAuthProvider { +func NewOidcAuthSetter(baseAuth BaseAuth, clientId string, clientSecret string, audience string, tokenEndpointUrl string) *OidcAuthProvider { tokenGenerator := &clientcredentials.Config{ ClientID: clientId, ClientSecret: clientSecret, @@ -38,8 +39,8 @@ func NewOidcAuthSetter(clientId string, clientSecret string, audience string, to } return &OidcAuthProvider{ - tokenGenerator: tokenGenerator, - authenticateHeartBeats: authenticateHeartBeats, + BaseAuth: baseAuth, + tokenGenerator: tokenGenerator, } } @@ -65,13 +66,23 @@ func (auth *OidcAuthProvider) SetPing(pingMsg *msg.Ping) (err error) { return err } -type OidcAuthConsumer struct { - verifier *oidc.IDTokenVerifier - authenticateHeartBeats bool - subjectFromLogin string +func (auth *OidcAuthProvider) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) (err error) { + if !auth.authenticateNewWorkConns { + return nil + } + + newWorkConnMsg.PrivilegeKey, err = auth.generateAccessToken() + return err } -func NewOidcAuthVerifier(issuer string, audience string, skipExpiryCheck bool, skipIssuerCheck bool, authenticateHeartBeats bool) *OidcAuthConsumer { +type OidcAuthConsumer struct { + BaseAuth + + verifier *oidc.IDTokenVerifier + subjectFromLogin string +} + +func NewOidcAuthVerifier(baseAuth BaseAuth, issuer string, audience string, skipExpiryCheck bool, skipIssuerCheck bool) *OidcAuthConsumer { provider, err := oidc.NewProvider(context.Background(), issuer) if err != nil { panic(err) @@ -83,8 +94,8 @@ func NewOidcAuthVerifier(issuer string, audience string, skipExpiryCheck bool, s SkipIssuerCheck: skipIssuerCheck, } return &OidcAuthConsumer{ - verifier: provider.Verifier(&verifierConf), - authenticateHeartBeats: authenticateHeartBeats, + BaseAuth: baseAuth, + verifier: provider.Verifier(&verifierConf), } } @@ -97,12 +108,8 @@ func (auth *OidcAuthConsumer) VerifyLogin(loginMsg *msg.Login) (err error) { return nil } -func (auth *OidcAuthConsumer) VerifyPing(pingMsg *msg.Ping) (err error) { - if !auth.authenticateHeartBeats { - return nil - } - - token, err := auth.verifier.Verify(context.Background(), pingMsg.PrivilegeKey) +func (auth *OidcAuthConsumer) verifyPostLoginToken(privilegeKey string) (err error) { + token, err := auth.verifier.Verify(context.Background(), privilegeKey) if err != nil { return fmt.Errorf("invalid OIDC token in ping: %v", err) } @@ -114,3 +121,19 @@ func (auth *OidcAuthConsumer) VerifyPing(pingMsg *msg.Ping) (err error) { } return nil } + +func (auth *OidcAuthConsumer) VerifyPing(pingMsg *msg.Ping) (err error) { + if !auth.authenticateHeartBeats { + return nil + } + + return auth.verifyPostLoginToken(pingMsg.PrivilegeKey) +} + +func (auth *OidcAuthConsumer) VerifyNewWorkConn(newWorkConnMsg *msg.NewWorkConn) (err error) { + if !auth.authenticateNewWorkConns { + return nil + } + + return auth.verifyPostLoginToken(newWorkConnMsg.PrivilegeKey) +} diff --git a/models/auth/token.go b/models/auth/token.go index 0f10020f..c1d494f7 100644 --- a/models/auth/token.go +++ b/models/auth/token.go @@ -22,37 +22,46 @@ import ( ) type TokenAuthSetterVerifier struct { - Token string + BaseAuth + + token string } -func NewTokenAuth(token string) *TokenAuthSetterVerifier { +func NewTokenAuth(baseAuth BaseAuth, token string) *TokenAuthSetterVerifier { return &TokenAuthSetterVerifier{ - Token: token, + BaseAuth: baseAuth, + token: token, } } func (auth *TokenAuthSetterVerifier) SetLogin(loginMsg *msg.Login) (err error) { - loginMsg.PrivilegeKey = util.GetAuthKey(auth.Token, loginMsg.Timestamp) + loginMsg.PrivilegeKey = util.GetAuthKey(auth.token, loginMsg.Timestamp) return nil } func (auth *TokenAuthSetterVerifier) SetPing(*msg.Ping) error { - // ping doesn't include authentication in token method + // Ping doesn't include authentication in token method return nil } -type TokenAuthConsumer struct { - Token string +func (auth *TokenAuthSetterVerifier) SetNewWorkConn(*msg.NewWorkConn) error { + // NewWorkConn doesn't include authentication in token method + return nil } func (auth *TokenAuthSetterVerifier) VerifyLogin(loginMsg *msg.Login) error { - if util.GetAuthKey(auth.Token, loginMsg.Timestamp) != loginMsg.PrivilegeKey { + if util.GetAuthKey(auth.token, loginMsg.Timestamp) != loginMsg.PrivilegeKey { return fmt.Errorf("token in login doesn't match token from configuration") } return nil } func (auth *TokenAuthSetterVerifier) VerifyPing(*msg.Ping) error { - // ping doesn't include authentication in token method + // Ping doesn't include authentication in token method + return nil +} + +func (auth *TokenAuthSetterVerifier) VerifyNewWorkConn(*msg.NewWorkConn) error { + // NewWorkConn doesn't include authentication in token method return nil } diff --git a/models/config/client_common.go b/models/config/client_common.go index 58e2bd66..5c68e56f 100644 --- a/models/config/client_common.go +++ b/models/config/client_common.go @@ -68,6 +68,9 @@ type ClientCommonConf struct { // AuthenticateHeartBeats specifies whether to include authentication token in // heartbeats sent to frps. By default, this value is false. AuthenticateHeartBeats bool `json:"authenticate_heartbeats"` + // AuthenticateNewWorkConns specifies whether to include authentication token in + // new work connections sent to frps. By default, this value is false. + AuthenticateNewWorkConns bool `json:"authenticate_new_work_conns"` // OidcClientId specifies the client ID to use to get a token in OIDC // authentication if AuthenticationMethod == "oidc". By default, this value @@ -147,37 +150,38 @@ type ClientCommonConf struct { // GetDefaultClientConf returns a client configuration with default values. func GetDefaultClientConf() ClientCommonConf { return ClientCommonConf{ - ServerAddr: "0.0.0.0", - ServerPort: 7000, - HttpProxy: os.Getenv("http_proxy"), - LogFile: "console", - LogWay: "console", - LogLevel: "info", - LogMaxDays: 3, - DisableLogColor: false, - Token: "", - AuthenticationMethod: "token", - AuthenticateHeartBeats: false, - OidcClientId: "", - OidcClientSecret: "", - OidcAudience: "", - OidcTokenEndpointUrl: "", - AdminAddr: "127.0.0.1", - AdminPort: 0, - AdminUser: "", - AdminPwd: "", - AssetsDir: "", - PoolCount: 1, - TcpMux: true, - User: "", - DnsServer: "", - LoginFailExit: true, - Start: make(map[string]struct{}), - Protocol: "tcp", - TLSEnable: false, - HeartBeatInterval: 30, - HeartBeatTimeout: 90, - Metas: make(map[string]string), + ServerAddr: "0.0.0.0", + ServerPort: 7000, + HttpProxy: os.Getenv("http_proxy"), + LogFile: "console", + LogWay: "console", + LogLevel: "info", + LogMaxDays: 3, + DisableLogColor: false, + Token: "", + AuthenticationMethod: "token", + AuthenticateHeartBeats: false, + AuthenticateNewWorkConns: false, + OidcClientId: "", + OidcClientSecret: "", + OidcAudience: "", + OidcTokenEndpointUrl: "", + AdminAddr: "127.0.0.1", + AdminPort: 0, + AdminUser: "", + AdminPwd: "", + AssetsDir: "", + PoolCount: 1, + TcpMux: true, + User: "", + DnsServer: "", + LoginFailExit: true, + Start: make(map[string]struct{}), + Protocol: "tcp", + TLSEnable: false, + HeartBeatInterval: 30, + HeartBeatTimeout: 90, + Metas: make(map[string]string), } } @@ -248,6 +252,12 @@ func UnmarshalClientConfFromIni(content string) (cfg ClientCommonConf, err error cfg.AuthenticateHeartBeats = false } + if tmpStr, ok = conf.Get("common", "authenticate_new_work_conns"); ok && tmpStr == "true" { + cfg.AuthenticateNewWorkConns = true + } else { + cfg.AuthenticateNewWorkConns = false + } + if tmpStr, ok = conf.Get("common", "oidc_client_id"); ok { cfg.OidcClientId = tmpStr } diff --git a/models/config/server_common.go b/models/config/server_common.go index 794337ae..065c78f8 100644 --- a/models/config/server_common.go +++ b/models/config/server_common.go @@ -113,6 +113,9 @@ type ServerCommonConf struct { // AuthenticateHeartBeats specifies whether to expect and verify authentication // token in heartbeats sent from frpc. By default, this value is false. AuthenticateHeartBeats bool `json:"authenticate_heartbeats"` + // AuthenticateNewWorkConns specifies whether to expect and verify authentication + // token in new work connections sent from frpc. By default, this value is false. + AuthenticateNewWorkConns bool `json:"authenticate_new_work_conns"` // OidcIssuer specifies the issuer to verify OIDC tokens with. This issuer // will be used to load public keys to verify signature and will be compared @@ -177,42 +180,43 @@ type ServerCommonConf struct { // defaults. func GetDefaultServerConf() ServerCommonConf { return ServerCommonConf{ - BindAddr: "0.0.0.0", - BindPort: 7000, - BindUdpPort: 0, - KcpBindPort: 0, - ProxyBindAddr: "0.0.0.0", - VhostHttpPort: 0, - VhostHttpsPort: 0, - VhostHttpTimeout: 60, - DashboardAddr: "0.0.0.0", - DashboardPort: 0, - DashboardUser: "admin", - DashboardPwd: "admin", - AssetsDir: "", - LogFile: "console", - LogWay: "console", - LogLevel: "info", - LogMaxDays: 3, - DisableLogColor: false, - DetailedErrorsToClient: true, - Token: "", - AuthenticationMethod: "token", - AuthenticateHeartBeats: false, - OidcIssuer: "", - OidcAudience: "", - OidcSkipExpiryCheck: false, - OidcSkipIssuerCheck: false, - SubDomainHost: "", - TcpMux: true, - AllowPorts: make(map[int]struct{}), - MaxPoolCount: 5, - MaxPortsPerClient: 0, - TlsOnly: false, - HeartBeatTimeout: 90, - UserConnTimeout: 10, - Custom404Page: "", - HTTPPlugins: make(map[string]plugin.HTTPPluginOptions), + BindAddr: "0.0.0.0", + BindPort: 7000, + BindUdpPort: 0, + KcpBindPort: 0, + ProxyBindAddr: "0.0.0.0", + VhostHttpPort: 0, + VhostHttpsPort: 0, + VhostHttpTimeout: 60, + DashboardAddr: "0.0.0.0", + DashboardPort: 0, + DashboardUser: "admin", + DashboardPwd: "admin", + AssetsDir: "", + LogFile: "console", + LogWay: "console", + LogLevel: "info", + LogMaxDays: 3, + DisableLogColor: false, + DetailedErrorsToClient: true, + Token: "", + AuthenticationMethod: "token", + AuthenticateHeartBeats: false, + AuthenticateNewWorkConns: false, + OidcIssuer: "", + OidcAudience: "", + OidcSkipExpiryCheck: false, + OidcSkipIssuerCheck: false, + SubDomainHost: "", + TcpMux: true, + AllowPorts: make(map[int]struct{}), + MaxPoolCount: 5, + MaxPortsPerClient: 0, + TlsOnly: false, + HeartBeatTimeout: 90, + UserConnTimeout: 10, + Custom404Page: "", + HTTPPlugins: make(map[string]plugin.HTTPPluginOptions), } } @@ -374,6 +378,12 @@ func UnmarshalServerConfFromIni(content string) (cfg ServerCommonConf, err error cfg.AuthenticateHeartBeats = false } + if tmpStr, ok = conf.Get("common", "authenticate_new_work_conns"); ok && tmpStr == "true" { + cfg.AuthenticateNewWorkConns = true + } else { + cfg.AuthenticateNewWorkConns = false + } + if tmpStr, ok = conf.Get("common", "oidc_issuer"); ok { cfg.OidcIssuer = tmpStr } diff --git a/models/msg/msg.go b/models/msg/msg.go index cba710fd..c416bf97 100644 --- a/models/msg/msg.go +++ b/models/msg/msg.go @@ -120,7 +120,8 @@ type CloseProxy struct { } type NewWorkConn struct { - RunId string `json:"run_id"` + RunId string `json:"run_id"` + PrivilegeKey string `json:"privilege_key"` } type ReqWorkConn struct { diff --git a/server/service.go b/server/service.go index 7ad5ac70..40712038 100644 --- a/server/service.go +++ b/server/service.go @@ -435,6 +435,11 @@ func (svr *Service) RegisterWorkConn(workConn net.Conn, newMsg *msg.NewWorkConn) xl.Warn("No client control found for run id [%s]", newMsg.RunId) return } + // Check auth. + if err := svr.authVerifier.VerifyNewWorkConn(newMsg); err != nil { + xl.Warn("Invalid authentication in NewWorkConn message on run id [%s]", newMsg.RunId) + return + } ctl.RegisterWorkConn(workConn) return }