feat: authenticate NewWorkConn messages, similar to ping

This commit is contained in:
Guy Lewin 2020-02-22 11:14:00 -05:00
parent 5544e5f7d5
commit 4877c78312
8 changed files with 180 additions and 101 deletions

View File

@ -142,6 +142,10 @@ func (ctl *Control) HandleReqWorkConn(inMsg *msg.ReqWorkConn) {
m := &msg.NewWorkConn{ m := &msg.NewWorkConn{
RunId: ctl.runId, 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 { if err = msg.WriteMsg(workConn, m); err != nil {
xl.Warn("work connection write to server error: %v", err) xl.Warn("work connection write to server error: %v", err)
workConn.Close() workConn.Close()

View File

@ -20,46 +20,63 @@ import (
"github.com/fatedier/frp/models/msg" "github.com/fatedier/frp/models/msg"
) )
type BaseAuth struct {
authenticateHeartBeats bool
authenticateNewWorkConns bool
}
type Setter interface { type Setter interface {
SetLogin(*msg.Login) error SetLogin(*msg.Login) error
SetPing(*msg.Ping) error SetPing(*msg.Ping) error
SetNewWorkConn(*msg.NewWorkConn) error
} }
func NewAuthSetter(cfg config.ClientCommonConf) (authProvider Setter) { func NewAuthSetter(cfg config.ClientCommonConf) (authProvider Setter) {
baseAuth := BaseAuth{
authenticateHeartBeats: cfg.AuthenticateHeartBeats,
authenticateNewWorkConns: cfg.AuthenticateNewWorkConns,
}
switch cfg.AuthenticationMethod { switch cfg.AuthenticationMethod {
case consts.TokenAuthMethod: case consts.TokenAuthMethod:
authProvider = NewTokenAuth(cfg.Token) authProvider = NewTokenAuth(baseAuth, cfg.Token)
case consts.OidcAuthMethod: case consts.OidcAuthMethod:
authProvider = NewOidcAuthSetter( authProvider = NewOidcAuthSetter(
baseAuth,
cfg.OidcClientId, cfg.OidcClientId,
cfg.OidcClientSecret, cfg.OidcClientSecret,
cfg.OidcAudience, cfg.OidcAudience,
cfg.OidcTokenEndpointUrl, cfg.OidcTokenEndpointUrl,
cfg.AuthenticateHeartBeats,
) )
} }
return return authProvider
} }
type Verifier interface { type Verifier interface {
VerifyLogin(*msg.Login) error VerifyLogin(*msg.Login) error
VerifyPing(*msg.Ping) error VerifyPing(*msg.Ping) error
VerifyNewWorkConn(*msg.NewWorkConn) error
} }
func NewAuthVerifier(cfg config.ServerCommonConf) (authVerifier Verifier) { func NewAuthVerifier(cfg config.ServerCommonConf) (authVerifier Verifier) {
baseAuth := BaseAuth{
authenticateHeartBeats: cfg.AuthenticateHeartBeats,
authenticateNewWorkConns: cfg.AuthenticateNewWorkConns,
}
switch cfg.AuthenticationMethod { switch cfg.AuthenticationMethod {
case consts.TokenAuthMethod: case consts.TokenAuthMethod:
authVerifier = NewTokenAuth(cfg.Token) authVerifier = NewTokenAuth(baseAuth, cfg.Token)
case consts.OidcAuthMethod: case consts.OidcAuthMethod:
authVerifier = NewOidcAuthVerifier( authVerifier = NewOidcAuthVerifier(
baseAuth,
cfg.OidcIssuer, cfg.OidcIssuer,
cfg.OidcAudience, cfg.OidcAudience,
cfg.OidcSkipExpiryCheck, cfg.OidcSkipExpiryCheck,
cfg.OidcSkipIssuerCheck, cfg.OidcSkipIssuerCheck,
cfg.AuthenticateHeartBeats,
) )
} }
return return authVerifier
} }

View File

@ -25,11 +25,12 @@ import (
) )
type OidcAuthProvider struct { type OidcAuthProvider struct {
tokenGenerator *clientcredentials.Config BaseAuth
authenticateHeartBeats bool
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{ tokenGenerator := &clientcredentials.Config{
ClientID: clientId, ClientID: clientId,
ClientSecret: clientSecret, ClientSecret: clientSecret,
@ -38,8 +39,8 @@ func NewOidcAuthSetter(clientId string, clientSecret string, audience string, to
} }
return &OidcAuthProvider{ return &OidcAuthProvider{
tokenGenerator: tokenGenerator, BaseAuth: baseAuth,
authenticateHeartBeats: authenticateHeartBeats, tokenGenerator: tokenGenerator,
} }
} }
@ -65,13 +66,23 @@ func (auth *OidcAuthProvider) SetPing(pingMsg *msg.Ping) (err error) {
return err return err
} }
type OidcAuthConsumer struct { func (auth *OidcAuthProvider) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) (err error) {
verifier *oidc.IDTokenVerifier if !auth.authenticateNewWorkConns {
authenticateHeartBeats bool return nil
subjectFromLogin string }
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) provider, err := oidc.NewProvider(context.Background(), issuer)
if err != nil { if err != nil {
panic(err) panic(err)
@ -83,8 +94,8 @@ func NewOidcAuthVerifier(issuer string, audience string, skipExpiryCheck bool, s
SkipIssuerCheck: skipIssuerCheck, SkipIssuerCheck: skipIssuerCheck,
} }
return &OidcAuthConsumer{ return &OidcAuthConsumer{
verifier: provider.Verifier(&verifierConf), BaseAuth: baseAuth,
authenticateHeartBeats: authenticateHeartBeats, verifier: provider.Verifier(&verifierConf),
} }
} }
@ -97,12 +108,8 @@ func (auth *OidcAuthConsumer) VerifyLogin(loginMsg *msg.Login) (err error) {
return nil return nil
} }
func (auth *OidcAuthConsumer) VerifyPing(pingMsg *msg.Ping) (err error) { func (auth *OidcAuthConsumer) verifyPostLoginToken(privilegeKey string) (err error) {
if !auth.authenticateHeartBeats { token, err := auth.verifier.Verify(context.Background(), privilegeKey)
return nil
}
token, err := auth.verifier.Verify(context.Background(), pingMsg.PrivilegeKey)
if err != nil { if err != nil {
return fmt.Errorf("invalid OIDC token in ping: %v", err) 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 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)
}

View File

@ -22,37 +22,46 @@ import (
) )
type TokenAuthSetterVerifier struct { type TokenAuthSetterVerifier struct {
Token string BaseAuth
token string
} }
func NewTokenAuth(token string) *TokenAuthSetterVerifier { func NewTokenAuth(baseAuth BaseAuth, token string) *TokenAuthSetterVerifier {
return &TokenAuthSetterVerifier{ return &TokenAuthSetterVerifier{
Token: token, BaseAuth: baseAuth,
token: token,
} }
} }
func (auth *TokenAuthSetterVerifier) SetLogin(loginMsg *msg.Login) (err error) { 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 return nil
} }
func (auth *TokenAuthSetterVerifier) SetPing(*msg.Ping) error { 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 return nil
} }
type TokenAuthConsumer struct { func (auth *TokenAuthSetterVerifier) SetNewWorkConn(*msg.NewWorkConn) error {
Token string // NewWorkConn doesn't include authentication in token method
return nil
} }
func (auth *TokenAuthSetterVerifier) VerifyLogin(loginMsg *msg.Login) error { 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 fmt.Errorf("token in login doesn't match token from configuration")
} }
return nil return nil
} }
func (auth *TokenAuthSetterVerifier) VerifyPing(*msg.Ping) error { 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 return nil
} }

View File

@ -68,6 +68,9 @@ type ClientCommonConf struct {
// AuthenticateHeartBeats specifies whether to include authentication token in // AuthenticateHeartBeats specifies whether to include authentication token in
// heartbeats sent to frps. By default, this value is false. // heartbeats sent to frps. By default, this value is false.
AuthenticateHeartBeats bool `json:"authenticate_heartbeats"` 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 // OidcClientId specifies the client ID to use to get a token in OIDC
// authentication if AuthenticationMethod == "oidc". By default, this value // authentication if AuthenticationMethod == "oidc". By default, this value
@ -147,37 +150,38 @@ type ClientCommonConf struct {
// GetDefaultClientConf returns a client configuration with default values. // GetDefaultClientConf returns a client configuration with default values.
func GetDefaultClientConf() ClientCommonConf { func GetDefaultClientConf() ClientCommonConf {
return ClientCommonConf{ return ClientCommonConf{
ServerAddr: "0.0.0.0", ServerAddr: "0.0.0.0",
ServerPort: 7000, ServerPort: 7000,
HttpProxy: os.Getenv("http_proxy"), HttpProxy: os.Getenv("http_proxy"),
LogFile: "console", LogFile: "console",
LogWay: "console", LogWay: "console",
LogLevel: "info", LogLevel: "info",
LogMaxDays: 3, LogMaxDays: 3,
DisableLogColor: false, DisableLogColor: false,
Token: "", Token: "",
AuthenticationMethod: "token", AuthenticationMethod: "token",
AuthenticateHeartBeats: false, AuthenticateHeartBeats: false,
OidcClientId: "", AuthenticateNewWorkConns: false,
OidcClientSecret: "", OidcClientId: "",
OidcAudience: "", OidcClientSecret: "",
OidcTokenEndpointUrl: "", OidcAudience: "",
AdminAddr: "127.0.0.1", OidcTokenEndpointUrl: "",
AdminPort: 0, AdminAddr: "127.0.0.1",
AdminUser: "", AdminPort: 0,
AdminPwd: "", AdminUser: "",
AssetsDir: "", AdminPwd: "",
PoolCount: 1, AssetsDir: "",
TcpMux: true, PoolCount: 1,
User: "", TcpMux: true,
DnsServer: "", User: "",
LoginFailExit: true, DnsServer: "",
Start: make(map[string]struct{}), LoginFailExit: true,
Protocol: "tcp", Start: make(map[string]struct{}),
TLSEnable: false, Protocol: "tcp",
HeartBeatInterval: 30, TLSEnable: false,
HeartBeatTimeout: 90, HeartBeatInterval: 30,
Metas: make(map[string]string), HeartBeatTimeout: 90,
Metas: make(map[string]string),
} }
} }
@ -248,6 +252,12 @@ func UnmarshalClientConfFromIni(content string) (cfg ClientCommonConf, err error
cfg.AuthenticateHeartBeats = false 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 { if tmpStr, ok = conf.Get("common", "oidc_client_id"); ok {
cfg.OidcClientId = tmpStr cfg.OidcClientId = tmpStr
} }

View File

@ -113,6 +113,9 @@ type ServerCommonConf struct {
// AuthenticateHeartBeats specifies whether to expect and verify authentication // AuthenticateHeartBeats specifies whether to expect and verify authentication
// token in heartbeats sent from frpc. By default, this value is false. // token in heartbeats sent from frpc. By default, this value is false.
AuthenticateHeartBeats bool `json:"authenticate_heartbeats"` 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 // 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 // will be used to load public keys to verify signature and will be compared
@ -177,42 +180,43 @@ type ServerCommonConf struct {
// defaults. // defaults.
func GetDefaultServerConf() ServerCommonConf { func GetDefaultServerConf() ServerCommonConf {
return ServerCommonConf{ return ServerCommonConf{
BindAddr: "0.0.0.0", BindAddr: "0.0.0.0",
BindPort: 7000, BindPort: 7000,
BindUdpPort: 0, BindUdpPort: 0,
KcpBindPort: 0, KcpBindPort: 0,
ProxyBindAddr: "0.0.0.0", ProxyBindAddr: "0.0.0.0",
VhostHttpPort: 0, VhostHttpPort: 0,
VhostHttpsPort: 0, VhostHttpsPort: 0,
VhostHttpTimeout: 60, VhostHttpTimeout: 60,
DashboardAddr: "0.0.0.0", DashboardAddr: "0.0.0.0",
DashboardPort: 0, DashboardPort: 0,
DashboardUser: "admin", DashboardUser: "admin",
DashboardPwd: "admin", DashboardPwd: "admin",
AssetsDir: "", AssetsDir: "",
LogFile: "console", LogFile: "console",
LogWay: "console", LogWay: "console",
LogLevel: "info", LogLevel: "info",
LogMaxDays: 3, LogMaxDays: 3,
DisableLogColor: false, DisableLogColor: false,
DetailedErrorsToClient: true, DetailedErrorsToClient: true,
Token: "", Token: "",
AuthenticationMethod: "token", AuthenticationMethod: "token",
AuthenticateHeartBeats: false, AuthenticateHeartBeats: false,
OidcIssuer: "", AuthenticateNewWorkConns: false,
OidcAudience: "", OidcIssuer: "",
OidcSkipExpiryCheck: false, OidcAudience: "",
OidcSkipIssuerCheck: false, OidcSkipExpiryCheck: false,
SubDomainHost: "", OidcSkipIssuerCheck: false,
TcpMux: true, SubDomainHost: "",
AllowPorts: make(map[int]struct{}), TcpMux: true,
MaxPoolCount: 5, AllowPorts: make(map[int]struct{}),
MaxPortsPerClient: 0, MaxPoolCount: 5,
TlsOnly: false, MaxPortsPerClient: 0,
HeartBeatTimeout: 90, TlsOnly: false,
UserConnTimeout: 10, HeartBeatTimeout: 90,
Custom404Page: "", UserConnTimeout: 10,
HTTPPlugins: make(map[string]plugin.HTTPPluginOptions), Custom404Page: "",
HTTPPlugins: make(map[string]plugin.HTTPPluginOptions),
} }
} }
@ -374,6 +378,12 @@ func UnmarshalServerConfFromIni(content string) (cfg ServerCommonConf, err error
cfg.AuthenticateHeartBeats = false 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 { if tmpStr, ok = conf.Get("common", "oidc_issuer"); ok {
cfg.OidcIssuer = tmpStr cfg.OidcIssuer = tmpStr
} }

View File

@ -120,7 +120,8 @@ type CloseProxy struct {
} }
type NewWorkConn struct { type NewWorkConn struct {
RunId string `json:"run_id"` RunId string `json:"run_id"`
PrivilegeKey string `json:"privilege_key"`
} }
type ReqWorkConn struct { type ReqWorkConn struct {

View File

@ -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) xl.Warn("No client control found for run id [%s]", newMsg.RunId)
return 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) ctl.RegisterWorkConn(workConn)
return return
} }