mirror of
https://github.com/fatedier/frp.git
synced 2026-03-19 16:29:14 +08:00
auth/oidc: fix eager token fetch at startup, add validation and e2e tests (#5234)
This commit is contained in:
192
test/e2e/v1/basic/oidc.go
Normal file
192
test/e2e/v1/basic/oidc.go
Normal file
@@ -0,0 +1,192 @@
|
||||
// Copyright 2026 The frp Authors
|
||||
//
|
||||
// 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 basic
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/onsi/ginkgo/v2"
|
||||
|
||||
"github.com/fatedier/frp/test/e2e/framework"
|
||||
"github.com/fatedier/frp/test/e2e/framework/consts"
|
||||
"github.com/fatedier/frp/test/e2e/mock/server/oidcserver"
|
||||
"github.com/fatedier/frp/test/e2e/pkg/port"
|
||||
)
|
||||
|
||||
var _ = ginkgo.Describe("[Feature: OIDC]", func() {
|
||||
f := framework.NewDefaultFramework()
|
||||
|
||||
ginkgo.It("should work with OIDC authentication", func() {
|
||||
oidcSrv := oidcserver.New(oidcserver.WithBindPort(f.AllocPort()))
|
||||
f.RunServer("", oidcSrv)
|
||||
|
||||
portName := port.GenName("TCP")
|
||||
|
||||
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
|
||||
auth.method = "oidc"
|
||||
auth.oidc.issuer = "%s"
|
||||
auth.oidc.audience = "frps"
|
||||
`, oidcSrv.Issuer())
|
||||
|
||||
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
|
||||
auth.method = "oidc"
|
||||
auth.oidc.clientID = "test-client"
|
||||
auth.oidc.clientSecret = "test-secret"
|
||||
auth.oidc.tokenEndpointURL = "%s"
|
||||
|
||||
[[proxies]]
|
||||
name = "tcp"
|
||||
type = "tcp"
|
||||
localPort = {{ .%s }}
|
||||
remotePort = {{ .%s }}
|
||||
`, oidcSrv.TokenEndpoint(), framework.TCPEchoServerPort, portName)
|
||||
|
||||
f.RunProcesses(serverConf, []string{clientConf})
|
||||
framework.NewRequestExpect(f).PortName(portName).Ensure()
|
||||
})
|
||||
|
||||
ginkgo.It("should authenticate heartbeats with OIDC", func() {
|
||||
oidcSrv := oidcserver.New(oidcserver.WithBindPort(f.AllocPort()))
|
||||
f.RunServer("", oidcSrv)
|
||||
|
||||
serverPort := f.AllocPort()
|
||||
remotePort := f.AllocPort()
|
||||
|
||||
serverConf := fmt.Sprintf(`
|
||||
bindAddr = "0.0.0.0"
|
||||
bindPort = %d
|
||||
log.level = "trace"
|
||||
auth.method = "oidc"
|
||||
auth.additionalScopes = ["HeartBeats"]
|
||||
auth.oidc.issuer = "%s"
|
||||
auth.oidc.audience = "frps"
|
||||
`, serverPort, oidcSrv.Issuer())
|
||||
|
||||
clientConf := fmt.Sprintf(`
|
||||
serverAddr = "127.0.0.1"
|
||||
serverPort = %d
|
||||
loginFailExit = false
|
||||
log.level = "trace"
|
||||
auth.method = "oidc"
|
||||
auth.additionalScopes = ["HeartBeats"]
|
||||
auth.oidc.clientID = "test-client"
|
||||
auth.oidc.clientSecret = "test-secret"
|
||||
auth.oidc.tokenEndpointURL = "%s"
|
||||
transport.heartbeatInterval = 1
|
||||
|
||||
[[proxies]]
|
||||
name = "tcp"
|
||||
type = "tcp"
|
||||
localPort = %d
|
||||
remotePort = %d
|
||||
`, serverPort, oidcSrv.TokenEndpoint(), f.PortByName(framework.TCPEchoServerPort), remotePort)
|
||||
|
||||
serverConfigPath := f.GenerateConfigFile(serverConf)
|
||||
clientConfigPath := f.GenerateConfigFile(clientConf)
|
||||
|
||||
_, _, err := f.RunFrps("-c", serverConfigPath)
|
||||
framework.ExpectNoError(err)
|
||||
clientProcess, _, err := f.RunFrpc("-c", clientConfigPath)
|
||||
framework.ExpectNoError(err)
|
||||
|
||||
// Wait for several authenticated heartbeat cycles instead of a fixed sleep.
|
||||
err = clientProcess.WaitForOutput("send heartbeat to server", 3, 10*time.Second)
|
||||
framework.ExpectNoError(err)
|
||||
|
||||
// Proxy should still work: heartbeat auth has not failed.
|
||||
framework.NewRequestExpect(f).Port(remotePort).Ensure()
|
||||
})
|
||||
|
||||
ginkgo.It("should work when token has no expires_in", func() {
|
||||
oidcSrv := oidcserver.New(
|
||||
oidcserver.WithBindPort(f.AllocPort()),
|
||||
oidcserver.WithExpiresIn(0),
|
||||
)
|
||||
f.RunServer("", oidcSrv)
|
||||
|
||||
portName := port.GenName("TCP")
|
||||
|
||||
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
|
||||
auth.method = "oidc"
|
||||
auth.oidc.issuer = "%s"
|
||||
auth.oidc.audience = "frps"
|
||||
`, oidcSrv.Issuer())
|
||||
|
||||
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
|
||||
auth.method = "oidc"
|
||||
auth.additionalScopes = ["HeartBeats"]
|
||||
auth.oidc.clientID = "test-client"
|
||||
auth.oidc.clientSecret = "test-secret"
|
||||
auth.oidc.tokenEndpointURL = "%s"
|
||||
transport.heartbeatInterval = 1
|
||||
|
||||
[[proxies]]
|
||||
name = "tcp"
|
||||
type = "tcp"
|
||||
localPort = {{ .%s }}
|
||||
remotePort = {{ .%s }}
|
||||
`, oidcSrv.TokenEndpoint(), framework.TCPEchoServerPort, portName)
|
||||
|
||||
_, clientProcesses := f.RunProcesses(serverConf, []string{clientConf})
|
||||
framework.NewRequestExpect(f).PortName(portName).Ensure()
|
||||
|
||||
countAfterLogin := oidcSrv.TokenRequestCount()
|
||||
|
||||
// Wait for several heartbeat cycles instead of a fixed sleep.
|
||||
// Each heartbeat fetches a fresh token in non-caching mode.
|
||||
err := clientProcesses[0].WaitForOutput("send heartbeat to server", 3, 10*time.Second)
|
||||
framework.ExpectNoError(err)
|
||||
|
||||
framework.NewRequestExpect(f).PortName(portName).Ensure()
|
||||
|
||||
// Each heartbeat should have fetched a new token (non-caching mode).
|
||||
countAfterHeartbeats := oidcSrv.TokenRequestCount()
|
||||
framework.ExpectTrue(
|
||||
countAfterHeartbeats > countAfterLogin,
|
||||
"expected additional token requests for heartbeats, got %d before and %d after",
|
||||
countAfterLogin, countAfterHeartbeats,
|
||||
)
|
||||
})
|
||||
|
||||
ginkgo.It("should reject invalid OIDC credentials", func() {
|
||||
oidcSrv := oidcserver.New(oidcserver.WithBindPort(f.AllocPort()))
|
||||
f.RunServer("", oidcSrv)
|
||||
|
||||
portName := port.GenName("TCP")
|
||||
|
||||
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
|
||||
auth.method = "oidc"
|
||||
auth.oidc.issuer = "%s"
|
||||
auth.oidc.audience = "frps"
|
||||
`, oidcSrv.Issuer())
|
||||
|
||||
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
|
||||
auth.method = "oidc"
|
||||
auth.oidc.clientID = "test-client"
|
||||
auth.oidc.clientSecret = "wrong-secret"
|
||||
auth.oidc.tokenEndpointURL = "%s"
|
||||
|
||||
[[proxies]]
|
||||
name = "tcp"
|
||||
type = "tcp"
|
||||
localPort = {{ .%s }}
|
||||
remotePort = {{ .%s }}
|
||||
`, oidcSrv.TokenEndpoint(), framework.TCPEchoServerPort, portName)
|
||||
|
||||
f.RunProcesses(serverConf, []string{clientConf})
|
||||
framework.NewRequestExpect(f).PortName(portName).ExpectError(true).Ensure()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user