Skip to content

Commit

Permalink
feat: Support custom public address in generated configurations
Browse files Browse the repository at this point in the history
Fixes #1
  • Loading branch information
guzalv authored and rg0now committed Jan 23, 2025
1 parent 8023c98 commit 460af1e
Show file tree
Hide file tree
Showing 12 changed files with 270 additions and 0 deletions.
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

0 comments on commit 460af1e

Please sign in to comment.