feat: authenticate NewWorkConn messages, similar to ping
This commit is contained in:
parent
5544e5f7d5
commit
4877c78312
@ -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()
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user