Skip to content
Closed
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
129 changes: 129 additions & 0 deletions .github/workflows/manual_prod_build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
name: Manual Build Production
on:
workflow_dispatch:
inputs:
branch:
description: "Branch to run the workflow"
required: true
docker_tag:
description: "Tag to be used by the docker image on push"
required: true
jobs:
docker_x86_release:
runs-on: ubuntu-latest
timeout-minutes: 120
env:
arch: amd64
outputs:
image_digest: ${{ steps.build.outputs.digest }}
steps:
- id: meta
uses: docker/metadata-action@v4
with:
images: |
supabase/realtime
tags: |
type=raw,value=v${{ github.event.inputs.docker_tag }}_${{ env.arch }}

- uses: docker/setup-buildx-action@v2

- uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

- id: build
uses: docker/build-push-action@v3
with:
push: true
tags: ${{ steps.meta.outputs.tags }}
platforms: linux/${{ env.arch }}
cache-from: type=gha
cache-to: type=gha,mode=max

docker_arm_release:
runs-on: arm-runner
timeout-minutes: 120
env:
arch: arm64
outputs:
image_digest: ${{ steps.build.outputs.digest }}
steps:
- uses: actions/checkout@v3

- id: meta
uses: docker/metadata-action@v4
with:
images: |
supabase/realtime
tags: |
type=raw,value=v${{ github.event.inputs.docker_tag }}_${{ env.arch }}

- uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

- uses: docker/setup-buildx-action@v2
with:
driver: docker
driver-opts: |
image=moby/buildkit:master
network=host

- id: build
uses: docker/build-push-action@v3
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
platforms: linux/${{ env.arch }}
no-cache: true

merge_manifest:
needs: [docker_x86_release, docker_arm_release]
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
steps:
- uses: docker/setup-buildx-action@v2

- uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

- name: Merge multi-arch manifests for custom output
run: |
docker buildx imagetools create -t supabase/realtime:v${{ github.event.inputs.docker_tag }} \
supabase/realtime@${{ needs.docker_x86_release.outputs.image_digest }} \
supabase/realtime@${{ needs.docker_arm_release.outputs.image_digest }}

- name: configure aws credentials
uses: aws-actions/configure-aws-credentials@v1
with:
role-to-assume: ${{ secrets.PROD_AWS_ROLE }}
aws-region: us-east-1

- name: Login to ECR
uses: docker/login-action@v2
with:
registry: public.ecr.aws

- name: Login to GHCR
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Mirror to ECR
uses: akhilerm/tag-push-action@v2.0.0
with:
src: docker.io/supabase/realtime:v${{ github.event.inputs.docker_tag }}
dst: |
public.ecr.aws/supabase/realtime:v${{ github.event.inputs.docker_tag }}
ghcr.io/supabase/realtime:v${{ github.event.inputs.docker_tag }}

12 changes: 8 additions & 4 deletions lib/realtime_web/channels/auth/jwt_verification.ex
Original file line number Diff line number Diff line change
Expand Up @@ -107,13 +107,17 @@ defmodule RealtimeWeb.JwtVerification do
"keys" => keys
})
when is_binary(kid) and alg in @hs_algorithms do
jwk = Enum.find(keys, fn jwk -> jwk["kty"] == "oct" and jwk["kid"] == kid end)
jwk = Enum.find(keys, fn jwk -> jwk["kty"] == "oct" and jwk["kid"] == kid and is_binary(jwk["k"]) end)

case jwk do
if jwk do
case Base.url_decode64(jwk["k"], padding: false) do
{:ok, secret} -> {:ok, Joken.Signer.create(alg, secret)}
_ -> {:error, :error_generating_signer}
end
else
# If there's no JWK, and HS* is being used, instead of erroring, try
# the jwt_secret instead.
nil -> {:ok, Joken.Signer.create(alg, jwt_secret)}
_ -> {:ok, Joken.Signer.create(alg, jwk)}
{:ok, Joken.Signer.create(alg, jwt_secret)}
end
end

Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ defmodule Realtime.MixProject do
def project do
[
app: :realtime,
version: "2.40.11",
version: "2.40.12",
elixir: "~> 1.17.3",
elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod,
Expand Down
86 changes: 83 additions & 3 deletions test/realtime_web/channels/auth/jwt_verification_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ defmodule RealtimeWeb.JwtVerificationTest do
setup_all do
Application.put_env(:realtime, :jwt_secret, @jwt_secret)
Application.put_env(:realtime, :jwt_claim_validators, %{})
on_exit(fn -> Application.put_env(:realtime, :jwt_claim_validators, %{}) end)
:ok
end

setup do
start_supervised(Mock)
on_exit(fn -> Application.put_env(:realtime, :jwt_claim_validators, %{}) end)
:ok
end

Expand Down Expand Up @@ -218,7 +218,26 @@ defmodule RealtimeWeb.JwtVerificationTest do
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImtleS1pZC0xIn0.eyJpYXQiOjE3MTIwNDc1NjUsInJvbGUiOiJhdXRoZW50aWNhdGVkIiwic3ViIjoidXNlci1pZCIsImV4cCI6MTcxMjA1MTE2NX0.zUeoZrWK1efAc4q9y978_9qkhdXktdjf5H8O9Rw0SHcPaXW8OBcuNR2huRrgORvqFx6_sHn6nCJaWkZGzO-f8wskMD7Z4INq2JUypr6nASie3Qu2lLyeY3WTInaXNAKH-oqlfTLRskbz8zkIxOj2bBJiN9ceQLkJU-c92ndiuiG5D1jyQrGsvRdFem_cemp0yOoEaC0XWdjeV6C_UD-34GIyv3o8H4HZg1GcCiyNnAfDmLAcTOQPmqkwsRDQb-pm5O3HwpQt9WHOB6i1vzf-nmIGyCRA7STPdALK16-aiAyT4SJRxM5WN3iK8yitH7g4JETb9WocBbwIM_zfNnUI5w"

# Check that the signature is valid even though time may be off.
assert {:error, :signature_error} != JwtVerification.verify(token, @jwt_secret, jwks)
assert JwtVerification.verify(token, @jwt_secret, jwks) != {:error, :signature_error}
end

test "using RS256 JWK but wrong signature" do
jwks = %{
"keys" => [
%{
"kty" => "RSA",
"n" =>
"6r1mKwCalvJ0NyThyQkBr5huFILwwhXcxtsdlw-WybNz4avzODQwLFkA-b2fnnfdFgualV2NdcvoJSo1bzVGCWWqwWKWdTQKFjtcjAIC4FnhOv5ynNF9Ub-11ORDd1aiq_4XKNA4GaS1HqBekVDAAvJYy99Jz0CkLx4NU_VrS0U9sOQzUAhy2MwZCx2kZ3SWKEMjjEIkbvIb22IdRTyuFsAndKGpyzhw-MalnU5P2hOig-QApNBc0WJtTHTAa4PLQ6v_5jNc5PzCwP8jGK9SlrSF-GOnx9BVBX9t-AIDp-BviKbtY7y-pku6-f7HSiS2T3iAJkHXPm9E_NwwhWzMJQ",
"e" => "AQAB",
"kid" => "key-id-1"
}
]
}

token =
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImtleS1pZC0xIn0.eyJpYXQiOjE3MTIwNDc1NjUsInJvbGUiOiJhdXRoZW50aWNhdGVkIiwic3ViIjoidXNlci1pZCIsImV4cCI6MTcxMjA1MTE2NX0.zUeoZrWK1efAc4q9y978_9qkhdXktdjf5H8O9Rw0SHcPaXW8OBcuNR2huRrgORvqFx6_sHn6nCJaWkZGzO-f8wskMD7Z4INq2JUypr6nASie3Qu2lLyeY3WTInaXNAKH-oqlfTLRskbz8zkIxOj2bBJiN9ceQLkJU-c92ndiuiG5D1jyQrGsvRdFem_cemp0yOoEaC0XWdjeV6C_UD-34GIyv3o8H4HZg1GcCiyNnAfDmLAcTOQPmqkwsRDQb-pm5O3HwpQt9WHOB6i1vzf-nmIGyCRA7STPdALK16-aiAyT4SJRxM5WN3iK8yitH7g4JETb9WocBbwIM_zfnnUI5w"

assert JwtVerification.verify(token, @jwt_secret, jwks) == {:error, :signature_error}
end

test "using ES256 JWK" do
Expand All @@ -239,7 +258,68 @@ defmodule RealtimeWeb.JwtVerificationTest do
"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6ImtleS1pZC0xIn0.eyJpYXQiOjE3MTIwNDk2NTcsInJvbGUiOiJhdXRoZW50aWNhdGVkIiwic3ViIjoidXNlci1pZCIsImV4cCI6MTcxMjA1MzI1N30.IIQBuEiSnZacGMqiqsrLAeRGOjIaB4F3x1gnLN5zvhFryJ-6tdgu96lFv5HUF13IL2UfHWad0OuvoDt4DEHRxw"

# Check that the signature is valid even though time may be off.
assert {:error, :signature_error} != JwtVerification.verify(token, @jwt_secret, jwks)
assert JwtVerification.verify(token, @jwt_secret, jwks) != {:error, :signature_error}
end

test "using ES256 JWK with wrong signature" do
jwks = %{
"keys" => [
%{
"kty" => "EC",
"x" => "iX_niXPSL2nW-9IyCELzyceAtuE3B98pWML5tQGACD4",
"y" => "kT02DoLhXx6gtpkbrN8XwQ2wtzE6cDBaqlWgVXIeqV0",
"crv" => "P-256",
"d" => "FBVYnsYA2C3FTggEwV8kCRMo4FLl220_cWY2RdXyb_8",
"kid" => "key-id-1"
}
]
}

token =
"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6ImtleS1pZC0xIn0.eyJpYXQiOjE3MTIwNDk2NTcsInJvbGUiOiJhdXRoZW50aWNhdGVkIiwic3ViIjoidXNlci1pZCIsImV4cCI6MTcxMjA1MzI1N30.IIQBuEiSnZacGMqiqsrLAeRGOjIaB4F3x1gnLN5zvhFryJ-6tdgu96lFv5HUF13IL2UfHWad0OuvoDt4DEHrxw"

assert JwtVerification.verify(token, @jwt_secret, jwks) == {:error, :signature_error}
end

test "using HS256 JWK" do
jwks = %{
"keys" => [
%{
"alg" => "HS256",
"k" =>
"WWpiUEVXK2I4dVM1djkzMS9TWTNmb2RtcUtiZVh3NnBHS0JaS1JDMGpaODdhVHpaZ3N0Ly9yMG0wU1M4Z1U4OFE0aGdwclBMMzVRRU5ya253TWxhUlE9PQ",
"key_ops" => ["verify"],
"kid" => "4FcGwlBxkBV1bSZw",
"kty" => "oct"
}
]
}

token =
"eyJhbGciOiJIUzI1NiIsImtpZCI6IjRGY0d3bEJ4a0JWMWJTWnciLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2hqbmRnYWdpZGlwY3RxdXFxeXloLnN1cGFiYXNlLmNvL2F1dGgvdjEiLCJzdWIiOiJmZjA0NjVlMy1lZjk3LTRkYjItOWE1Zi0zZDI4Y2YxODE0MmYiLCJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNzUyODA4NjE4LCJpYXQiOjE3NTI4MDUwMTgsImVtYWlsIjoiY2hhdEBlZHVhcmRvLmd1cmdlbC5tZSIsInBob25lIjoiIiwiYXBwX21ldGFkYXRhIjp7InByb3ZpZGVyIjoiZW1haWwiLCJwcm92aWRlcnMiOlsiZW1haWwiXX0sInVzZXJfbWV0YWRhdGEiOnsiZW1haWwiOiJjaGF0QGVkdWFyZG8uZ3VyZ2VsLm1lIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsInBob25lX3ZlcmlmaWVkIjpmYWxzZSwic3ViIjoiZmYwNDY1ZTMtZWY5Ny00ZGIyLTlhNWYtM2QyOGNmMTgxNDJmIn0sInJvbGUiOiJhdXRoZW50aWNhdGVkIiwiYWFsIjoiYWFsMSIsImFtciI6W3sibWV0aG9kIjoicGFzc3dvcmQiLCJ0aW1lc3RhbXAiOjE3NTI4MDUwMTh9XSwic2Vzc2lvbl9pZCI6IjA2MDJkYWM0LWMwMjctNGIwNi1hZDM5LTMzN2ViMTZlODdlNSIsImlzX2Fub255bW91cyI6ZmFsc2V9.SnGzRjLfHPtT64kXYEQVBLKizCl76LqEPILyAPxoDwk"

# Check that the signature is valid even though time may be off.
assert JwtVerification.verify(token, @jwt_secret, jwks) != {:error, :signature_error}
end

test "using HS256 JWK with wrong signature" do
jwks = %{
"keys" => [
%{
"alg" => "HS256",
"k" =>
"WWpiUEVXK2I4dVM1djkzMS9TWTNmb2RtcUtiZVh3NnBHS0JaS1JDMGpaODdhVHpaZ3N0Ly9yMG0wU1M4Z1U4OFE0aGdwclBMMzVRRU5ya253TWxhUlE9PQ",
"key_ops" => ["verify"],
"kid" => "4FcGwlBxkBV1bSZw",
"kty" => "oct"
}
]
}

token =
"eyJhbGciOiJIUzI1NiIsImtpZCI6IjRGY0d3bEJ4a0JWMWJTWnciLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2hqbmRnYWdpZGlwY3RxdXFxeXloLnN1cGFiYXNlLmNvL2F1dGgvdjEiLCJzdWIiOiJmZjA0NjVlMy1lZjk3LTRkYjItOWE1Zi0zZDI4Y2YxODE0MmYiLCJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNzUyODA4NjE4LCJpYXQiOjE3NTI4MDUwMTgsImVtYWlsIjoiY2hhdEBlZHVhcmRvLmd1cmdlbC5tZSIsInBob25lIjoiIiwiYXBwX21ldGFkYXRhIjp7InByb3ZpZGVyIjoiZW1haWwiLCJwcm92aWRlcnMiOlsiZW1haWwiXX0sInVzZXJfbWV0YWRhdGEiOnsiZW1haWwiOiJjaGF0QGVkdWFyZG8uZ3VyZ2VsLm1lIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsInBob25lX3ZlcmlmaWVkIjpmYWxzZSwic3ViIjoiZmYwNDY1ZTMtZWY5Ny00ZGIyLTlhNWYtM2QyOGNmMTgxNDJmIn0sInJvbGUiOiJhdXRoZW50aWNhdGVkIiwiYWFsIjoiYWFsMSIsImFtciI6W3sibWV0aG9kIjoicGFzc3dvcmQiLCJ0aW1lc3RhbXAiOjE3NTI4MDUwMTh9XSwic2Vzc2lvbl9pZCI6IjA2MDJkYWM0LWMwMjctNGIwNi1hZDM5LTMzN2ViMTZlODdlNSIsImlzX2Fub255bW91cyI6ZmFsc2V9.SnGzRjLfHPtT64kXYEQVBLKizCl76LqEPILyApxoDwk"

assert JwtVerification.verify(token, @jwt_secret, jwks) == {:error, :signature_error}
end

test "returns error when no matching JWK is found for RSA algorithm" do
Expand Down
Loading