Compare commits

...

3 Commits

6 changed files with 125 additions and 13 deletions

View File

@ -50,7 +50,8 @@ func NewAuthVerifier(cfg v1.AuthServerConfig) (authVerifier Verifier) {
case v1.AuthMethodToken: case v1.AuthMethodToken:
authVerifier = NewTokenAuth(cfg.AdditionalScopes, cfg.Token) authVerifier = NewTokenAuth(cfg.AdditionalScopes, cfg.Token)
case v1.AuthMethodOIDC: case v1.AuthMethodOIDC:
authVerifier = NewOidcAuthVerifier(cfg.AdditionalScopes, cfg.OIDC) tokenVerifier := NewTokenVerifier(cfg.OIDC)
authVerifier = NewOidcAuthVerifier(cfg.AdditionalScopes, tokenVerifier)
} }
return authVerifier return authVerifier
} }

View File

@ -87,14 +87,18 @@ func (auth *OidcAuthProvider) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) (e
return err return err
} }
type TokenVerifier interface {
Verify(context.Context, string) (*oidc.IDToken, error)
}
type OidcAuthConsumer struct { type OidcAuthConsumer struct {
additionalAuthScopes []v1.AuthScope additionalAuthScopes []v1.AuthScope
verifier *oidc.IDTokenVerifier verifier TokenVerifier
subjectFromLogin string subjectsFromLogin []string
} }
func NewOidcAuthVerifier(additionalAuthScopes []v1.AuthScope, cfg v1.AuthOIDCServerConfig) *OidcAuthConsumer { func NewTokenVerifier(cfg v1.AuthOIDCServerConfig) TokenVerifier {
provider, err := oidc.NewProvider(context.Background(), cfg.Issuer) provider, err := oidc.NewProvider(context.Background(), cfg.Issuer)
if err != nil { if err != nil {
panic(err) panic(err)
@ -105,9 +109,14 @@ func NewOidcAuthVerifier(additionalAuthScopes []v1.AuthScope, cfg v1.AuthOIDCSer
SkipExpiryCheck: cfg.SkipExpiryCheck, SkipExpiryCheck: cfg.SkipExpiryCheck,
SkipIssuerCheck: cfg.SkipIssuerCheck, SkipIssuerCheck: cfg.SkipIssuerCheck,
} }
return provider.Verifier(&verifierConf)
}
func NewOidcAuthVerifier(additionalAuthScopes []v1.AuthScope, verifier TokenVerifier) *OidcAuthConsumer {
return &OidcAuthConsumer{ return &OidcAuthConsumer{
additionalAuthScopes: additionalAuthScopes, additionalAuthScopes: additionalAuthScopes,
verifier: provider.Verifier(&verifierConf), verifier: verifier,
subjectsFromLogin: []string{},
} }
} }
@ -116,7 +125,9 @@ func (auth *OidcAuthConsumer) VerifyLogin(loginMsg *msg.Login) (err error) {
if err != nil { if err != nil {
return fmt.Errorf("invalid OIDC token in login: %v", err) return fmt.Errorf("invalid OIDC token in login: %v", err)
} }
auth.subjectFromLogin = token.Subject if !slices.Contains(auth.subjectsFromLogin, token.Subject) {
auth.subjectsFromLogin = append(auth.subjectsFromLogin, token.Subject)
}
return nil return nil
} }
@ -125,11 +136,11 @@ func (auth *OidcAuthConsumer) verifyPostLoginToken(privilegeKey string) (err err
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)
} }
if token.Subject != auth.subjectFromLogin { if !slices.Contains(auth.subjectsFromLogin, token.Subject) {
return fmt.Errorf("received different OIDC subject in login and ping. "+ return fmt.Errorf("received different OIDC subject in login and ping. "+
"original subject: %s, "+ "original subjects: %s, "+
"new subject: %s", "new subject: %s",
auth.subjectFromLogin, token.Subject) auth.subjectsFromLogin, token.Subject)
} }
return nil return nil
} }

64
pkg/auth/oidc_test.go Normal file
View File

@ -0,0 +1,64 @@
package auth_test
import (
"context"
"testing"
"time"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/stretchr/testify/require"
"github.com/fatedier/frp/pkg/auth"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/msg"
)
type mockTokenVerifier struct{}
func (m *mockTokenVerifier) Verify(ctx context.Context, subject string) (*oidc.IDToken, error) {
return &oidc.IDToken{
Subject: subject,
}, nil
}
func TestPingWithEmptySubjectFromLoginFails(t *testing.T) {
r := require.New(t)
consumer := auth.NewOidcAuthVerifier([]v1.AuthScope{v1.AuthScopeHeartBeats}, &mockTokenVerifier{})
err := consumer.VerifyPing(&msg.Ping{
PrivilegeKey: "ping-without-login",
Timestamp: time.Now().UnixMilli(),
})
r.Error(err)
r.Contains(err.Error(), "received different OIDC subject in login and ping")
}
func TestPingAfterLoginWithNewSubjectSucceeds(t *testing.T) {
r := require.New(t)
consumer := auth.NewOidcAuthVerifier([]v1.AuthScope{v1.AuthScopeHeartBeats}, &mockTokenVerifier{})
err := consumer.VerifyLogin(&msg.Login{
PrivilegeKey: "ping-after-login",
})
r.NoError(err)
err = consumer.VerifyPing(&msg.Ping{
PrivilegeKey: "ping-after-login",
Timestamp: time.Now().UnixMilli(),
})
r.NoError(err)
}
func TestPingAfterLoginWithDifferentSubjectFails(t *testing.T) {
r := require.New(t)
consumer := auth.NewOidcAuthVerifier([]v1.AuthScope{v1.AuthScopeHeartBeats}, &mockTokenVerifier{})
err := consumer.VerifyLogin(&msg.Login{
PrivilegeKey: "login-with-first-subject",
})
r.NoError(err)
err = consumer.VerifyPing(&msg.Ping{
PrivilegeKey: "ping-with-different-subject",
Timestamp: time.Now().UnixMilli(),
})
r.Error(err)
r.Contains(err.Error(), "received different OIDC subject in login and ping")
}

View File

@ -103,6 +103,7 @@ type HTTP2HTTPSPluginOptions struct {
LocalAddr string `json:"localAddr,omitempty"` LocalAddr string `json:"localAddr,omitempty"`
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"` HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"` RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
RootCA string `json:"rootCA,omitempty"`
} }
func (o *HTTP2HTTPSPluginOptions) Complete() {} func (o *HTTP2HTTPSPluginOptions) Complete() {}
@ -137,6 +138,7 @@ type HTTPS2HTTPSPluginOptions struct {
EnableHTTP2 *bool `json:"enableHTTP2,omitempty"` EnableHTTP2 *bool `json:"enableHTTP2,omitempty"`
CrtPath string `json:"crtPath,omitempty"` CrtPath string `json:"crtPath,omitempty"`
KeyPath string `json:"keyPath,omitempty"` KeyPath string `json:"keyPath,omitempty"`
RootCA string `json:"rootCA,omitempty"`
} }
func (o *HTTPS2HTTPSPluginOptions) Complete() { func (o *HTTPS2HTTPSPluginOptions) Complete() {

View File

@ -19,11 +19,13 @@ package plugin
import ( import (
"context" "context"
"crypto/tls" "crypto/tls"
"crypto/x509"
"io" "io"
stdlog "log" stdlog "log"
"net" "net"
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
"os"
"github.com/fatedier/golib/pool" "github.com/fatedier/golib/pool"
@ -53,8 +55,23 @@ func NewHTTP2HTTPSPlugin(options v1.ClientPluginOptions) (Plugin, error) {
l: listener, l: listener,
} }
tr := &http.Transport{ tr := &http.Transport{}
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
if opts.RootCA != "" {
caCert, err := os.ReadFile(opts.RootCA)
if err != nil {
return nil, err
}
caCertPool, err := x509.SystemCertPool()
if err != nil {
return nil, err
}
caCertPool.AppendCertsFromPEM(caCert)
tr.TLSClientConfig = &tls.Config{
RootCAs: caCertPool,
}
} else {
tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
} }
rp := &httputil.ReverseProxy{ rp := &httputil.ReverseProxy{

View File

@ -19,12 +19,14 @@ package plugin
import ( import (
"context" "context"
"crypto/tls" "crypto/tls"
"crypto/x509"
"fmt" "fmt"
"io" "io"
stdlog "log" stdlog "log"
"net" "net"
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
"os"
"time" "time"
"github.com/fatedier/golib/pool" "github.com/fatedier/golib/pool"
@ -58,8 +60,23 @@ func NewHTTPS2HTTPSPlugin(options v1.ClientPluginOptions) (Plugin, error) {
l: listener, l: listener,
} }
tr := &http.Transport{ tr := &http.Transport{}
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
if opts.RootCA != "" {
caCert, err := os.ReadFile(opts.RootCA)
if err != nil {
return nil, err
}
caCertPool, err := x509.SystemCertPool()
if err != nil {
return nil, err
}
caCertPool.AppendCertsFromPEM(caCert)
tr.TLSClientConfig = &tls.Config{
RootCAs: caCertPool,
}
} else {
tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
} }
rp := &httputil.ReverseProxy{ rp := &httputil.ReverseProxy{