dot_files/dot_claude/skills/webrtc-turn-shared-secret/SKILL.md
Viktor Barzin c95ffa03c5 migrate cc-config to chezmoi: add all skills, agents, and openclaw installer
- Add 4 missing skills: chromedp-alpine-container, claude-memory-api,
  openclaw-custom-model-provider, webrtc-turn-shared-secret
- Add 9 custom agents: sre, dba, devops-engineer, platform-engineer,
  security-engineer, network-engineer, observability-engineer,
  home-automation-engineer, cluster-health-checker
- Add openclaw-install.sh: standalone script to clone dotfiles and
  install skills/agents/hooks/settings to OpenClaw's home directory
  Replaces the cc-config NFS volume + sync.sh approach
2026-03-15 16:02:05 +00:00

105 lines
3.6 KiB
Markdown

---
name: webrtc-turn-shared-secret
description: |
Generate ephemeral TURN credentials from a shared secret for coturn (--use-auth-secret mode).
Use when: (1) WebRTC ICE connection state goes to "failed" or stays at "checking",
(2) STUN-only config can't establish media path through NAT/k8s,
(3) coturn is configured with --use-auth-secret and you need time-limited credentials,
(4) need to pass TURN credentials to both server-side (pion/webrtc) and client-side
(browser RTCPeerConnection). Covers credential generation, Go implementation, and
client-side WebRTC configuration.
author: Claude Code
version: 1.0.0
date: 2026-02-21
---
# WebRTC TURN Server with Shared Secret Credentials
## Problem
WebRTC connections fail with `ICE connection state: failed` when peers are behind NAT
(especially in Kubernetes pods). STUN alone can't establish a media path through
symmetric NAT. A TURN server is needed, and coturn's shared secret mode requires
generating ephemeral credentials.
## Context / Trigger Conditions
- `webrtc: ICE connection state: failed` in server logs
- `ICE connection state: failed` in browser console
- WebRTC signaling (offer/answer) succeeds but no media flows
- Server is in a k8s pod with private IP, client is behind NAT
- coturn configured with `--use-auth-secret` or `use-auth-secret` in turnserver.conf
## Solution
### Credential Generation (TURN REST API)
```
username = Unix timestamp of expiry (e.g., "1740200000")
password = Base64(HMAC-SHA1(username, shared_secret))
```
### Go Implementation
```go
import (
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"fmt"
"time"
)
func GenerateTURNCredentials(turnURL, sharedSecret string, ttl time.Duration) (urls []string, username, credential string) {
expiry := time.Now().Add(ttl).Unix()
username = fmt.Sprintf("%d", expiry)
mac := hmac.New(sha1.New, []byte(sharedSecret))
mac.Write([]byte(username))
credential = base64.StdEncoding.EncodeToString(mac.Sum(nil))
return []string{turnURL}, username, credential
}
```
### Server-side (pion/webrtc)
```go
iceServers := []webrtc.ICEServer{
{URLs: []string{"stun:stun.l.google.com:19302"}},
{
URLs: []string{"turn:your-turn-server:3478"},
Username: username,
Credential: credential,
CredentialType: webrtc.ICECredentialTypePassword,
},
}
pc, _ := webrtc.NewPeerConnection(webrtc.Configuration{ICEServers: iceServers})
```
### Client-side (browser)
Send ICE config from server to client via signaling channel (WebSocket),
then create RTCPeerConnection with it:
```javascript
// Server sends: { type: "iceServers", iceServers: [...] }
socket.onmessage = (e) => {
const msg = JSON.parse(e.data);
if (msg.type === 'iceServers') {
pc = new RTCPeerConnection({ iceServers: msg.iceServers });
}
};
```
## Verification
1. Server logs should show `ICE connection state: connected` (not `failed`)
2. Browser console should show `ICE connection state: connected`
3. Test TURN connectivity: `turnutils_uclient -u username -w credential turn-server-ip`
## Notes
- Both server and client need the TURN credentials — the server uses them for its
PeerConnection, and the client needs them for its RTCPeerConnection
- Credentials are time-limited (TTL); generate fresh ones per session
- If TURN server hostname doesn't resolve from k8s pods (CoreDNS custom zones),
use the IP address directly: `turn:1.2.3.4:3478`
- STUN is still useful as a fallback for direct connections; keep it in the ICE
servers list alongside TURN
- The shared secret must match coturn's `static-auth-secret` config