Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support custom public address in generated configurations #8

Merged
merged 1 commit into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,20 @@ this API too. In addition, the parameter `iceTransportPolicy=relay` will force c
generating host and server-reflexive ICE candidates and
use TURN for connecting unconditionally. This will make client connections much faster.

### Ensuring valid Gateway public IP addresses

In certain scenarios, STUNner is unable to determine the public IP for a Gateway
(e.g. private Kubernetes clusters without LoadBalancer and node ExternalIPs). In
these cases the public IP can be manually set using the following methods, listed in
order of priority (from highest to lowest):

- Setting the [`public-addr` URL parameter](#request) when requesting configurations.
- Setting the `STUNNER_PUBLIC_ADDR` environment variable (see the
"stunner-auth-server" container in the [Kubernetes manifest](deploy/kubernetes-stunner-auth-service.yaml)).

If multiple methods are used, the one with the highest priority will override the rest
(e.g., setting the `public-addr` parameter always takes precedence).

## API

The REST API exposes two API endpoints: `getTurnAuth` can be called to obtain a TURN authentication
Expand Down Expand Up @@ -226,6 +240,7 @@ A request to the `getTurnAuth` API endpoint includes the following parameters, s
- `listener`: consider only the specified listener on the given STUNner Gateway; if `listener` is
set then `namespace` and `gateway` must be set too.
- `ttl`: the requested lifetime of the credential. Default is one day, make sure to customize.
- `public-addr`: override the public IP address with the provided value.

### Response

Expand Down
12 changes: 12 additions & 0 deletions api/stunner.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ paths:
required: false
schema:
type: string
- name: public-addr
in: query
description: Override the public IP address with the provided value (optional)
required: false
schema:
type: string
responses:
"200":
description: Successful operation
Expand Down Expand Up @@ -151,6 +157,12 @@ paths:
required: false
schema:
type: string
- name: public-addr
in: query
description: Override the public IP address with the provided value (optional)
required: false
schema:
type: string
responses:
"200":
description: Successful operation
Expand Down
5 changes: 5 additions & 0 deletions deploy/kubernetes-stunner-auth-service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ spec:
imagePullPolicy: Always
# image: localhost/l7mp/stunner-auth-server
# imagePullPolicy: Never
env:
# Overrides the public IP of the Gateway. Useful when STUNner cannot
# determine a valid public IP
- name: STUNNER_PUBLIC_ADDR
value: ""
command: [ "./manager" ]
# args: ["-zap-log-level","info"]
# max loglevel
Expand Down
91 changes: 91 additions & 0 deletions ice_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/l7mp/stunner/pkg/logger"

// "github.com/l7mp/stunner-auth-service/pkg/client"
"github.com/l7mp/stunner-auth-service/internal/config"
"github.com/l7mp/stunner-auth-service/internal/handler"
"github.com/l7mp/stunner-auth-service/pkg/server"
"github.com/l7mp/stunner-auth-service/pkg/types"
Expand All @@ -32,6 +33,7 @@ import (
type iceAuthTestCase struct {
name string
config []*stnrv1.StunnerConfig
envPublicAddr string
params string
status int
tester func(t *testing.T, iceAuth *types.IceConfig, authHandler a12n.AuthHandler)
Expand Down Expand Up @@ -462,6 +464,83 @@ var iceAuthTestCases = []iceAuthTestCase{
status: 404,
tester: func(t *testing.T, iceConfig *types.IceConfig, authHandler a12n.AuthHandler) {},
},
{
name: "static - public IP set via URL parameter",
config: []*stnrv1.StunnerConfig{&staticAuthConfig},
params: "service=turn&public-addr=1.3.5.7",
status: 200,
tester: func(t *testing.T, iceConfig *types.IceConfig, authHandler a12n.AuthHandler) {
assert.NotNil(t, iceConfig, "ICE config nil")
assert.NotNil(t, iceConfig.IceServers, "ICE servers nil")
iceServers := *iceConfig.IceServers
assert.Len(t, iceServers, 1, "ICE servers len")
iceAuth := iceServers[0]
assert.NotNil(t, iceAuth, "ICE auth token nil")
assert.NotNil(t, iceAuth.Username, "username nil")
assert.Equal(t, "user1", *iceAuth.Username, "username nil")
assert.NotNil(t, iceAuth.Credential, "credential nil")
assert.Equal(t, "pass1", *iceAuth.Credential, "credential ok")
assert.NotNil(t, iceAuth.Urls, "URLs nil")
uris := *iceAuth.Urls
assert.Len(t, uris, 4, "URI len")
assert.Contains(t, uris, "turn:1.3.5.7:3478?transport=udp", "UDP URI")
assert.Contains(t, uris, "turn:1.3.5.7:3478?transport=tcp", "TCP URI")
assert.Contains(t, uris, "turns:1.3.5.7:3479?transport=tcp", "TLS URI")
assert.Contains(t, uris, "turns:1.3.5.7:3479?transport=udp", "DTLS URI")
},
},
{
name: "static - public IP set via env var",
config: []*stnrv1.StunnerConfig{&staticAuthConfig},
envPublicAddr: "2.4.6.8",
params: "service=turn",
status: 200,
tester: func(t *testing.T, iceConfig *types.IceConfig, authHandler a12n.AuthHandler) {
assert.NotNil(t, iceConfig, "ICE config nil")
assert.NotNil(t, iceConfig.IceServers, "ICE servers nil")
iceServers := *iceConfig.IceServers
assert.Len(t, iceServers, 1, "ICE servers len")
iceAuth := iceServers[0]
assert.NotNil(t, iceAuth, "ICE auth token nil")
assert.NotNil(t, iceAuth.Username, "username nil")
assert.Equal(t, "user1", *iceAuth.Username, "username nil")
assert.NotNil(t, iceAuth.Credential, "credential nil")
assert.Equal(t, "pass1", *iceAuth.Credential, "credential ok")
assert.NotNil(t, iceAuth.Urls, "URLs nil")
uris := *iceAuth.Urls
assert.Len(t, uris, 4, "URI len")
assert.Contains(t, uris, "turn:2.4.6.8:3478?transport=udp", "UDP URI")
assert.Contains(t, uris, "turn:2.4.6.8:3478?transport=tcp", "TCP URI")
assert.Contains(t, uris, "turns:2.4.6.8:3479?transport=tcp", "TLS URI")
assert.Contains(t, uris, "turns:2.4.6.8:3479?transport=udp", "DTLS URI")
},
},
{
name: "static - public IP set via URL parameter takes precedence over env var",
config: []*stnrv1.StunnerConfig{&staticAuthConfig},
envPublicAddr: "2.4.6.8",
params: "service=turn&public-addr=1.3.5.7",
status: 200,
tester: func(t *testing.T, iceConfig *types.IceConfig, authHandler a12n.AuthHandler) {
assert.NotNil(t, iceConfig, "ICE config nil")
assert.NotNil(t, iceConfig.IceServers, "ICE servers nil")
iceServers := *iceConfig.IceServers
assert.Len(t, iceServers, 1, "ICE servers len")
iceAuth := iceServers[0]
assert.NotNil(t, iceAuth, "ICE auth token nil")
assert.NotNil(t, iceAuth.Username, "username nil")
assert.Equal(t, "user1", *iceAuth.Username, "username nil")
assert.NotNil(t, iceAuth.Credential, "credential nil")
assert.Equal(t, "pass1", *iceAuth.Credential, "credential ok")
assert.NotNil(t, iceAuth.Urls, "URLs nil")
uris := *iceAuth.Urls
assert.Len(t, uris, 4, "URI len")
assert.Contains(t, uris, "turn:1.3.5.7:3478?transport=udp", "UDP URI")
assert.Contains(t, uris, "turn:1.3.5.7:3478?transport=tcp", "TCP URI")
assert.Contains(t, uris, "turns:1.3.5.7:3479?transport=tcp", "TLS URI")
assert.Contains(t, uris, "turns:1.3.5.7:3479?transport=udp", "DTLS URI")
},
},
}

func TestICEAuth(t *testing.T) { testICE(t, iceAuthTestCases) }
Expand Down Expand Up @@ -499,6 +578,12 @@ func testICE(t *testing.T, tests []iceAuthTestCase) {
t.Run(testCase.name, func(t *testing.T) {
log.Info(fmt.Sprintf("---------------- Test: %s ----------------", testCase.name))

if testCase.envPublicAddr != "" {
oldPublicAddr := config.PublicAddr
config.PublicAddr = testCase.envPublicAddr
t.Cleanup(func() { config.PublicAddr = oldPublicAddr })
}

log.Info("storing config")
handler.Reset()
for _, c := range testCase.config {
Expand Down Expand Up @@ -601,6 +686,12 @@ func testICECDS(t *testing.T, tests []iceAuthTestCase) {
t.Run(testCase.name, func(t *testing.T) {
log.Info(fmt.Sprintf("---------------- Test: %s ----------------", testCase.name))

if testCase.envPublicAddr != "" {
oldPublicAddr := config.PublicAddr
config.PublicAddr = testCase.envPublicAddr
t.Cleanup(func() { config.PublicAddr = oldPublicAddr })
}

log.Info("storing config")
cd := []cdsserver.Config{}
for _, c := range testCase.config {
Expand Down
32 changes: 32 additions & 0 deletions internal/client/client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions internal/config/defs.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ const (
// DefaultTimeout is the default TURN credential timeout. Default is one day, in seconds.
DefaultTimeout = 24 * time.Hour
)

var PublicAddr string
8 changes: 8 additions & 0 deletions internal/handler/ice.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,14 @@ func (h *Handler) getIceServerConfForStunnerConf(params types.GetIceAuthParams,
h.log.Debugf("Considering Listener: namespace: %s, gateway: %s, listener: %s", namespace,
gateway, listener)

if params.PublicAddr != nil {
l.PublicAddr = *params.PublicAddr
h.log.Debugf("Using public address from request: %s", l.PublicAddr)
} else if config.PublicAddr != "" {
l.PublicAddr = config.PublicAddr
h.log.Debugf("Using public address from environment: %s", l.PublicAddr)
}

// filter
if params.Namespace != nil && *params.Namespace != namespace {
h.log.Debugf("Ignoring listener due to gateway namespace mismatch: "+
Expand Down
1 change: 1 addition & 0 deletions internal/handler/turn.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func (h *Handler) GetTurnAuth(w http.ResponseWriter, r *http.Request, params typ
Namespace: params.Namespace,
Gateway: params.Gateway,
Listener: params.Listener,
PublicAddr: params.PublicAddr,
}

if h.NumConfig() == 0 {
Expand Down
6 changes: 6 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
cdsclient "github.com/l7mp/stunner/pkg/config/client"
"github.com/l7mp/stunner/pkg/logger"

"github.com/l7mp/stunner-auth-service/internal/config"
"github.com/l7mp/stunner-auth-service/internal/handler"
"github.com/l7mp/stunner-auth-service/pkg/server"
)
Expand Down Expand Up @@ -59,6 +60,11 @@ func main() {
loggerFactory := logger.NewLoggerFactory(logLevel)
log := loggerFactory.NewLogger("authd")

if envPublicAddr, present := os.LookupEnv("STUNNER_PUBLIC_ADDR"); present {
config.PublicAddr = envPublicAddr
log.Infof("Using STUNner public address from environment: %s", envPublicAddr)
}

conf := make(chan *stnrv1.StunnerConfig, 10)
defer close(conf)

Expand Down
16 changes: 16 additions & 0 deletions pkg/server/server.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions pkg/types/types.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading