diff --git a/.changeset/kind-flowers-obey.md b/.changeset/kind-flowers-obey.md new file mode 100644 index 000000000..00ce682ac --- /dev/null +++ b/.changeset/kind-flowers-obey.md @@ -0,0 +1,5 @@ +--- +"github.com/livekit/protocol": patch +--- + +allow credentials to be stored in SIP tokens diff --git a/auth/accesstoken.go b/auth/accesstoken.go index b6804fd77..a8aabb699 100644 --- a/auth/accesstoken.go +++ b/auth/accesstoken.go @@ -29,10 +29,11 @@ const ( // AccessToken produces token signed with API key and secret type AccessToken struct { - apiKey string - secret string - grant ClaimGrants - validFor time.Duration + apiKey string + secret string + grant ClaimGrants + validFor time.Duration + allowSensitiveCredentials bool } func NewAccessToken(key string, secret string) *AccessToken { @@ -138,6 +139,16 @@ func (t *AccessToken) SetAgents(agents ...*livekit.RoomAgentDispatch) *AccessTok return t } +// SetAllowSensitiveCredentials enables the token to contain sensitive credentials, by default it is disabled. +// When tokens are issued to end-users, it's not a good idea to issue sensitive data such as API keys/secrets in them +// JWT tokens are not encrypted, so anything that is issued in them can be read by anyone. +// When the tokens are used in a server environment (i.e. connecting from SIP or Agents), you can bypass the +// credentials check by enabling this option. +func (t *AccessToken) SetAllowSensitiveCredentials(allow bool) *AccessToken { + t.allowSensitiveCredentials = allow + return t +} + func (t *AccessToken) GetGrants() *ClaimGrants { return &t.grant } @@ -147,7 +158,7 @@ func (t *AccessToken) ToJWT() (string, error) { return "", ErrKeysMissing } - if t.grant.RoomConfig != nil { + if t.grant.RoomConfig != nil && !t.allowSensitiveCredentials { if err := t.grant.RoomConfig.CheckCredentials(); err != nil { return "", err } diff --git a/auth/accesstoken_test.go b/auth/accesstoken_test.go index 0e0198b8b..d6255d67f 100644 --- a/auth/accesstoken_test.go +++ b/auth/accesstoken_test.go @@ -179,6 +179,57 @@ func TestAccessToken(t *testing.T) { require.True(t, hasAgentName, "agentName should be present in camelCase") _, hasAgentNameSnakeCase := agent["agent_name"] require.False(t, hasAgentNameSnakeCase, "agent_name should not be present in snake_case") + + t.Run("room configuration blocks sensitive credentials by default", func(t *testing.T) { + apiKey, secret := apiKeypair() + roomConfig := &livekit.RoomConfiguration{ + Egress: &livekit.RoomEgress{ + Room: &livekit.RoomCompositeEgressRequest{ + FileOutputs: []*livekit.EncodedFileOutput{{ + Output: &livekit.EncodedFileOutput_S3{S3: &livekit.S3Upload{Secret: "super-secret"}}, + }}, + }, + }, + } + _, err := NewAccessToken(apiKey, secret). + SetVideoGrant(&VideoGrant{RoomJoin: true, Room: "test-room"}). + SetRoomConfig(roomConfig). + ToJWT() + require.ErrorIs(t, err, ErrSensitiveCredentials) + }) + + t.Run("room configuration allows sensitive credentials when enabled", func(t *testing.T) { + apiKey, secret := apiKeypair() + roomConfig := &livekit.RoomConfiguration{ + Egress: &livekit.RoomEgress{ + Room: &livekit.RoomCompositeEgressRequest{ + FileOutputs: []*livekit.EncodedFileOutput{{ + Output: &livekit.EncodedFileOutput_S3{S3: &livekit.S3Upload{Secret: "super-secret"}}, + }}, + }, + }, + } + value, err := NewAccessToken(apiKey, secret). + SetVideoGrant(&VideoGrant{RoomJoin: true, Room: "test-room"}). + SetRoomConfig(roomConfig). + SetAllowSensitiveCredentials(true). + ToJWT() + require.NoError(t, err) + + v, err := ParseAPIToken(value) + require.NoError(t, err) + claims, err := v.Verify(secret) + require.NoError(t, err) + + rc := (*livekit.RoomConfiguration)(claims.RoomConfig) + require.NotNil(t, rc) + require.NotNil(t, rc.Egress) + require.NotNil(t, rc.Egress.Room) + require.NotEmpty(t, rc.Egress.Room.FileOutputs) + s3Out, ok := rc.Egress.Room.FileOutputs[0].Output.(*livekit.EncodedFileOutput_S3) + require.True(t, ok) + require.Equal(t, "super-secret", s3Out.S3.Secret) + }) }) } diff --git a/sip/token.go b/sip/token.go index 0863d0430..4d3f6bc1d 100644 --- a/sip/token.go +++ b/sip/token.go @@ -51,7 +51,8 @@ func BuildSIPToken(params SIPTokenParams) (string, error) { SetRoomPreset(params.RoomPreset). SetRoomConfig(params.RoomConfig). SetKind(livekit.ParticipantInfo_SIP). - SetValidFor(24 * time.Hour) + SetValidFor(24 * time.Hour). + SetAllowSensitiveCredentials(true) return at.ToJWT() }