diff --git a/.gitguardian.yaml b/.gitguardian.yaml index d9d8d076a3..4c42272761 100644 --- a/.gitguardian.yaml +++ b/.gitguardian.yaml @@ -3,3 +3,4 @@ version: 2 secret: ignored_paths: - 'frontend/app/cypress/e2e/**' + - 'pkg/authmode/keyset/**' diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0aee8f52e9..d209f5e123 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -68,6 +68,33 @@ jobs: - name: Build servers run: docker build -f ./build/package/servers.dockerfile . --build-arg SERVER_TARGET=engine + authdisabled: + runs-on: ubuntu-latest + steps: + - name: Clone repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Build api (authdisabled) + run: docker build -f ./build/package/servers.dockerfile . --build-arg SERVER_TARGET=api --build-arg GO_BUILD_TAGS=authdisabled + - name: Build engine (authdisabled) + run: docker build -f ./build/package/servers.dockerfile . --build-arg SERVER_TARGET=engine --build-arg GO_BUILD_TAGS=authdisabled + - name: Build admin (authdisabled) + run: docker build -f ./build/package/servers.dockerfile . --build-arg SERVER_TARGET=admin --build-arg GO_BUILD_TAGS=authdisabled + - name: Build lite (authdisabled) + run: docker build -f ./build/package/servers.dockerfile . --build-arg SERVER_TARGET=lite --build-arg GO_BUILD_TAGS=authdisabled + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: '3.14' + - name: Generate docs snippets and examples + working-directory: frontend/snippets + run: python3 generate.py + - name: Build dashboard (authdisabled) + run: | + DOCKER_BUILDKIT=1 docker build -f ./build/package/servers.dockerfile \ + --build-arg SERVER_TARGET=api --build-arg GO_BUILD_TAGS=authdisabled \ + -t hatchet-api-dev-tmp . + DOCKER_BUILDKIT=1 docker build -f ./build/package/dashboard.dockerfile \ + --build-arg HATCHET_API_IMAGE=hatchet-api-dev-tmp . + admin: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/pre-release.yaml b/.github/workflows/pre-release.yaml index e40c1ed6de..397e169861 100644 --- a/.github/workflows/pre-release.yaml +++ b/.github/workflows/pre-release.yaml @@ -60,6 +60,7 @@ jobs: needs: - build-push-hatchet-api-amd - build-push-hatchet-api-arm + - auth-enabled steps: - name: Get tag name id: tag_name @@ -81,6 +82,79 @@ jobs: ghcr.io/hatchet-dev/hatchet/hatchet-api:${{steps.tag_name.outputs.tag}}-amd64 \ ghcr.io/hatchet-dev/hatchet/hatchet-api:${{steps.tag_name.outputs.tag}}-arm64 docker manifest push ghcr.io/hatchet-dev/hatchet/hatchet-api:${{steps.tag_name.outputs.tag}} + build-push-hatchet-api-dev-amd: + name: hatchet-api-dev + runs-on: ubuntu-latest + steps: + - name: Get tag name + id: tag_name + run: echo "tag=${GITHUB_TAG/refs\/tags\//}" >> $GITHUB_OUTPUT + env: + GITHUB_TAG: ${{ github.ref }} + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Login to GHCR + id: login-ghcr + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + - name: Build dev image + run: | + DOCKER_BUILDKIT=1 docker build -f ./build/package/servers.dockerfile \ + -t ghcr.io/hatchet-dev/hatchet/hatchet-api-dev:${{steps.tag_name.outputs.tag}}-amd64 \ + --build-arg SERVER_TARGET=api \ + --build-arg VERSION=${{steps.tag_name.outputs.tag}} \ + --build-arg GO_BUILD_TAGS=authdisabled \ + --platform linux/amd64 \ + . + - name: Push to GHCR + run: docker push ghcr.io/hatchet-dev/hatchet/hatchet-api-dev:${{steps.tag_name.outputs.tag}}-amd64 + build-push-hatchet-api-dev-arm: + name: hatchet-api-dev + runs-on: hatchet-arm64-2 + steps: + - name: Get tag name + id: tag_name + run: echo "tag=${GITHUB_TAG/refs\/tags\//}" >> $GITHUB_OUTPUT + env: + GITHUB_TAG: ${{ github.ref }} + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Login to GHCR + id: login-ghcr + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + - name: Build dev image + run: | + DOCKER_BUILDKIT=1 docker build -f ./build/package/servers.dockerfile \ + -t ghcr.io/hatchet-dev/hatchet/hatchet-api-dev:${{steps.tag_name.outputs.tag}}-arm64 \ + --build-arg SERVER_TARGET=api \ + --build-arg VERSION=${{steps.tag_name.outputs.tag}} \ + --build-arg GO_BUILD_TAGS=authdisabled \ + --platform linux/arm64 \ + . + - name: Push to GHCR + run: docker push ghcr.io/hatchet-dev/hatchet/hatchet-api-dev:${{steps.tag_name.outputs.tag}}-arm64 + build-push-hatchet-api-dev: + name: Combine hatchet-api-dev + runs-on: ubuntu-latest + needs: + - build-push-hatchet-api-dev-amd + - build-push-hatchet-api-dev-arm + steps: + - name: Get tag name + id: tag_name + run: echo "tag=${GITHUB_TAG/refs\/tags\//}" >> $GITHUB_OUTPUT + env: + GITHUB_TAG: ${{ github.ref }} + - name: Login to GHCR + id: login-ghcr + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + - name: Combine dev image + run: | + docker pull ghcr.io/hatchet-dev/hatchet/hatchet-api-dev:${{steps.tag_name.outputs.tag}}-amd64 + docker pull ghcr.io/hatchet-dev/hatchet/hatchet-api-dev:${{steps.tag_name.outputs.tag}}-arm64 + docker manifest create ghcr.io/hatchet-dev/hatchet/hatchet-api-dev:${{steps.tag_name.outputs.tag}} \ + ghcr.io/hatchet-dev/hatchet/hatchet-api-dev:${{steps.tag_name.outputs.tag}}-amd64 \ + ghcr.io/hatchet-dev/hatchet/hatchet-api-dev:${{steps.tag_name.outputs.tag}}-arm64 + docker manifest push ghcr.io/hatchet-dev/hatchet/hatchet-api-dev:${{steps.tag_name.outputs.tag}} build-push-hatchet-admin-amd: name: hatchet-admin runs-on: ubuntu-latest @@ -104,8 +178,7 @@ jobs: --platform linux/amd64 \ . - name: Push to GHCR - run: | - docker push ghcr.io/hatchet-dev/hatchet/hatchet-admin:${{steps.tag_name.outputs.tag}}-amd64 + run: docker push ghcr.io/hatchet-dev/hatchet/hatchet-admin:${{steps.tag_name.outputs.tag}}-amd64 build-push-hatchet-admin-arm: name: hatchet-admin runs-on: hatchet-arm64-2 @@ -129,8 +202,7 @@ jobs: --platform linux/arm64 \ . - name: Push to GHCR - run: | - docker push ghcr.io/hatchet-dev/hatchet/hatchet-admin:${{steps.tag_name.outputs.tag}}-arm64 + run: docker push ghcr.io/hatchet-dev/hatchet/hatchet-admin:${{steps.tag_name.outputs.tag}}-arm64 build-push-hatchet-admin: name: Combine hatchet-admin runs-on: ubuntu-latest @@ -158,6 +230,79 @@ jobs: ghcr.io/hatchet-dev/hatchet/hatchet-admin:${{steps.tag_name.outputs.tag}}-amd64 \ ghcr.io/hatchet-dev/hatchet/hatchet-admin:${{steps.tag_name.outputs.tag}}-arm64 docker manifest push ghcr.io/hatchet-dev/hatchet/hatchet-admin:${{steps.tag_name.outputs.tag}} + build-push-hatchet-admin-dev-amd: + name: hatchet-admin-dev + runs-on: ubuntu-latest + steps: + - name: Get tag name + id: tag_name + run: echo "tag=${GITHUB_TAG/refs\/tags\//}" >> $GITHUB_OUTPUT + env: + GITHUB_TAG: ${{ github.ref }} + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Login to GHCR + id: login-ghcr + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + - name: Build dev image + run: | + DOCKER_BUILDKIT=1 docker build -f ./build/package/servers.dockerfile \ + -t ghcr.io/hatchet-dev/hatchet/hatchet-admin-dev:${{steps.tag_name.outputs.tag}}-amd64 \ + --build-arg SERVER_TARGET=admin \ + --build-arg VERSION=${{steps.tag_name.outputs.tag}} \ + --build-arg GO_BUILD_TAGS=authdisabled \ + --platform linux/amd64 \ + . + - name: Push to GHCR + run: docker push ghcr.io/hatchet-dev/hatchet/hatchet-admin-dev:${{steps.tag_name.outputs.tag}}-amd64 + build-push-hatchet-admin-dev-arm: + name: hatchet-admin-dev + runs-on: hatchet-arm64-2 + steps: + - name: Get tag name + id: tag_name + run: echo "tag=${GITHUB_TAG/refs\/tags\//}" >> $GITHUB_OUTPUT + env: + GITHUB_TAG: ${{ github.ref }} + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Login to GHCR + id: login-ghcr + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + - name: Build dev image + run: | + DOCKER_BUILDKIT=1 docker build -f ./build/package/servers.dockerfile \ + -t ghcr.io/hatchet-dev/hatchet/hatchet-admin-dev:${{steps.tag_name.outputs.tag}}-arm64 \ + --build-arg SERVER_TARGET=admin \ + --build-arg VERSION=${{steps.tag_name.outputs.tag}} \ + --build-arg GO_BUILD_TAGS=authdisabled \ + --platform linux/arm64 \ + . + - name: Push to GHCR + run: docker push ghcr.io/hatchet-dev/hatchet/hatchet-admin-dev:${{steps.tag_name.outputs.tag}}-arm64 + build-push-hatchet-admin-dev: + name: Combine hatchet-admin-dev + runs-on: ubuntu-latest + needs: + - build-push-hatchet-admin-dev-amd + - build-push-hatchet-admin-dev-arm + steps: + - name: Get tag name + id: tag_name + run: echo "tag=${GITHUB_TAG/refs\/tags\//}" >> $GITHUB_OUTPUT + env: + GITHUB_TAG: ${{ github.ref }} + - name: Login to GHCR + id: login-ghcr + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + - name: Combine dev image + run: | + docker pull ghcr.io/hatchet-dev/hatchet/hatchet-admin-dev:${{steps.tag_name.outputs.tag}}-amd64 + docker pull ghcr.io/hatchet-dev/hatchet/hatchet-admin-dev:${{steps.tag_name.outputs.tag}}-arm64 + docker manifest create ghcr.io/hatchet-dev/hatchet/hatchet-admin-dev:${{steps.tag_name.outputs.tag}} \ + ghcr.io/hatchet-dev/hatchet/hatchet-admin-dev:${{steps.tag_name.outputs.tag}}-amd64 \ + ghcr.io/hatchet-dev/hatchet/hatchet-admin-dev:${{steps.tag_name.outputs.tag}}-arm64 + docker manifest push ghcr.io/hatchet-dev/hatchet/hatchet-admin-dev:${{steps.tag_name.outputs.tag}} build-push-hatchet-engine-amd: name: hatchet-engine runs-on: ubuntu-latest @@ -181,8 +326,7 @@ jobs: --platform linux/amd64 \ . - name: Push to GHCR - run: | - docker push ghcr.io/hatchet-dev/hatchet/hatchet-engine:${{steps.tag_name.outputs.tag}}-amd64 + run: docker push ghcr.io/hatchet-dev/hatchet/hatchet-engine:${{steps.tag_name.outputs.tag}}-amd64 build-push-hatchet-engine-arm: name: hatchet-engine runs-on: hatchet-arm64-2 @@ -206,8 +350,7 @@ jobs: --platform linux/arm64 \ . - name: Push to GHCR - run: | - docker push ghcr.io/hatchet-dev/hatchet/hatchet-engine:${{steps.tag_name.outputs.tag}}-arm64 + run: docker push ghcr.io/hatchet-dev/hatchet/hatchet-engine:${{steps.tag_name.outputs.tag}}-arm64 build-push-hatchet-engine: name: Combine hatchet-engine runs-on: ubuntu-latest @@ -235,6 +378,79 @@ jobs: ghcr.io/hatchet-dev/hatchet/hatchet-engine:${{steps.tag_name.outputs.tag}}-amd64 \ ghcr.io/hatchet-dev/hatchet/hatchet-engine:${{steps.tag_name.outputs.tag}}-arm64 docker manifest push ghcr.io/hatchet-dev/hatchet/hatchet-engine:${{steps.tag_name.outputs.tag}} + build-push-hatchet-engine-dev-amd: + name: hatchet-engine-dev + runs-on: ubuntu-latest + steps: + - name: Get tag name + id: tag_name + run: echo "tag=${GITHUB_TAG/refs\/tags\//}" >> $GITHUB_OUTPUT + env: + GITHUB_TAG: ${{ github.ref }} + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Login to GHCR + id: login-ghcr + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + - name: Build dev image + run: | + DOCKER_BUILDKIT=1 docker build -f ./build/package/servers.dockerfile \ + -t ghcr.io/hatchet-dev/hatchet/hatchet-engine-dev:${{steps.tag_name.outputs.tag}}-amd64 \ + --build-arg SERVER_TARGET=engine \ + --build-arg VERSION=${{steps.tag_name.outputs.tag}} \ + --build-arg GO_BUILD_TAGS=authdisabled \ + --platform linux/amd64 \ + . + - name: Push to GHCR + run: docker push ghcr.io/hatchet-dev/hatchet/hatchet-engine-dev:${{steps.tag_name.outputs.tag}}-amd64 + build-push-hatchet-engine-dev-arm: + name: hatchet-engine-dev + runs-on: hatchet-arm64-2 + steps: + - name: Get tag name + id: tag_name + run: echo "tag=${GITHUB_TAG/refs\/tags\//}" >> $GITHUB_OUTPUT + env: + GITHUB_TAG: ${{ github.ref }} + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Login to GHCR + id: login-ghcr + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + - name: Build dev image + run: | + DOCKER_BUILDKIT=1 docker build -f ./build/package/servers.dockerfile \ + -t ghcr.io/hatchet-dev/hatchet/hatchet-engine-dev:${{steps.tag_name.outputs.tag}}-arm64 \ + --build-arg SERVER_TARGET=engine \ + --build-arg VERSION=${{steps.tag_name.outputs.tag}} \ + --build-arg GO_BUILD_TAGS=authdisabled \ + --platform linux/arm64 \ + . + - name: Push to GHCR + run: docker push ghcr.io/hatchet-dev/hatchet/hatchet-engine-dev:${{steps.tag_name.outputs.tag}}-arm64 + build-push-hatchet-engine-dev: + name: Combine hatchet-engine-dev + runs-on: ubuntu-latest + needs: + - build-push-hatchet-engine-dev-amd + - build-push-hatchet-engine-dev-arm + steps: + - name: Get tag name + id: tag_name + run: echo "tag=${GITHUB_TAG/refs\/tags\//}" >> $GITHUB_OUTPUT + env: + GITHUB_TAG: ${{ github.ref }} + - name: Login to GHCR + id: login-ghcr + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + - name: Combine dev image + run: | + docker pull ghcr.io/hatchet-dev/hatchet/hatchet-engine-dev:${{steps.tag_name.outputs.tag}}-amd64 + docker pull ghcr.io/hatchet-dev/hatchet/hatchet-engine-dev:${{steps.tag_name.outputs.tag}}-arm64 + docker manifest create ghcr.io/hatchet-dev/hatchet/hatchet-engine-dev:${{steps.tag_name.outputs.tag}} \ + ghcr.io/hatchet-dev/hatchet/hatchet-engine-dev:${{steps.tag_name.outputs.tag}}-amd64 \ + ghcr.io/hatchet-dev/hatchet/hatchet-engine-dev:${{steps.tag_name.outputs.tag}}-arm64 + docker manifest push ghcr.io/hatchet-dev/hatchet/hatchet-engine-dev:${{steps.tag_name.outputs.tag}} build-push-hatchet-migrate-amd: name: hatchet-migrate runs-on: ubuntu-latest @@ -454,8 +670,7 @@ jobs: --build-arg HATCHET_MIGRATE_IMAGE=hatchet-migrate-tmp:amd64 \ . - name: Push to GHCR - run: | - docker push ghcr.io/hatchet-dev/hatchet/hatchet-lite:${{steps.tag_name.outputs.tag}}-amd64 + run: docker push ghcr.io/hatchet-dev/hatchet/hatchet-lite:${{steps.tag_name.outputs.tag}}-amd64 build-push-hatchet-lite-arm: name: hatchet-lite-arm runs-on: hatchet-arm64-2 @@ -513,14 +728,42 @@ jobs: --build-arg HATCHET_MIGRATE_IMAGE=hatchet-migrate-tmp:arm64 \ . - name: Push to GHCR - run: | - docker push ghcr.io/hatchet-dev/hatchet/hatchet-lite:${{steps.tag_name.outputs.tag}}-arm64 + run: docker push ghcr.io/hatchet-dev/hatchet/hatchet-lite:${{steps.tag_name.outputs.tag}}-arm64 + auth-enabled: + name: Verify auth enabled (non-dev) + runs-on: ubuntu-latest + needs: + - build-push-hatchet-api-amd + - build-push-hatchet-admin-amd + - build-push-hatchet-migrate-amd + - build-push-hatchet-lite-amd + - build-push-hatchet-dashboard-amd + steps: + - name: Get tag name + id: tag_name + run: echo "tag=${GITHUB_TAG/refs\/tags\//}" >> $GITHUB_OUTPUT + env: + GITHUB_TAG: ${{ github.ref }} + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Login to GHCR + id: login-ghcr + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + - name: Assert non-dev api, dashboard and lite have auth ENABLED + env: + API_IMAGE: ghcr.io/hatchet-dev/hatchet/hatchet-api:${{steps.tag_name.outputs.tag}}-amd64 + DASHBOARD_IMAGE: ghcr.io/hatchet-dev/hatchet/hatchet-dashboard:${{steps.tag_name.outputs.tag}}-amd64 + LITE_IMAGE: ghcr.io/hatchet-dev/hatchet/hatchet-lite:${{steps.tag_name.outputs.tag}}-amd64 + ADMIN_IMAGE: ghcr.io/hatchet-dev/hatchet/hatchet-admin:${{steps.tag_name.outputs.tag}}-amd64 + MIGRATE_IMAGE: ghcr.io/hatchet-dev/hatchet/hatchet-migrate:${{steps.tag_name.outputs.tag}}-amd64 + run: bash hack/ci/assert-auth-enabled.sh combine-hatchet-lite: name: Combine hatchet-lite runs-on: ubuntu-latest needs: - build-push-hatchet-lite-amd - build-push-hatchet-lite-arm + - auth-enabled steps: - name: Get tag name id: tag_name @@ -542,6 +785,149 @@ jobs: ghcr.io/hatchet-dev/hatchet/hatchet-lite:${{steps.tag_name.outputs.tag}}-amd64 \ ghcr.io/hatchet-dev/hatchet/hatchet-lite:${{steps.tag_name.outputs.tag}}-arm64 docker manifest push ghcr.io/hatchet-dev/hatchet/hatchet-lite:${{steps.tag_name.outputs.tag}} + build-push-hatchet-lite-dev-amd: + name: hatchet-lite-dev-amd + runs-on: ubuntu-latest + steps: + - name: Get tag name + id: tag_name + run: echo "tag=${GITHUB_TAG/refs\/tags\//}" >> $GITHUB_OUTPUT + env: + GITHUB_TAG: ${{ github.ref }} + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: '3.14' + - name: Generate docs snippets and examples + working-directory: frontend/snippets + run: python3 generate.py + - name: Login to GHCR + id: login-ghcr + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + - name: Set up QEMU + uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 + - name: Build dev image + run: | + DOCKER_BUILDKIT=1 docker build -f ./build/package/servers.dockerfile \ + --build-arg SERVER_TARGET=lite \ + --build-arg VERSION=${{steps.tag_name.outputs.tag}} \ + --build-arg GO_BUILD_TAGS=authdisabled \ + --platform linux/amd64 \ + -t hatchet-lite-dev-tmp:amd64 \ + . & + + DOCKER_BUILDKIT=1 docker build -f ./build/package/servers.dockerfile \ + --build-arg SERVER_TARGET=admin \ + --build-arg VERSION=${{steps.tag_name.outputs.tag}} \ + --build-arg GO_BUILD_TAGS=authdisabled \ + --platform linux/amd64 \ + -t hatchet-admin-dev-tmp:amd64 \ + . & + + DOCKER_BUILDKIT=1 docker build -f ./build/package/servers.dockerfile \ + --build-arg SERVER_TARGET=migrate \ + --build-arg VERSION=${{steps.tag_name.outputs.tag}} \ + --platform linux/amd64 \ + -t hatchet-migrate-tmp:amd64 \ + . & + + wait + + DOCKER_BUILDKIT=1 docker build -f ./build/package/lite.dockerfile \ + -t ghcr.io/hatchet-dev/hatchet/hatchet-lite-dev:${{steps.tag_name.outputs.tag}}-amd64 \ + --platform linux/amd64 \ + --build-arg HATCHET_LITE_IMAGE=hatchet-lite-dev-tmp:amd64 \ + --build-arg HATCHET_ADMIN_IMAGE=hatchet-admin-dev-tmp:amd64 \ + --build-arg HATCHET_MIGRATE_IMAGE=hatchet-migrate-tmp:amd64 \ + . + - name: Push to GHCR + run: docker push ghcr.io/hatchet-dev/hatchet/hatchet-lite-dev:${{steps.tag_name.outputs.tag}}-amd64 + build-push-hatchet-lite-dev-arm: + name: hatchet-lite-dev-arm + runs-on: hatchet-arm64-2 + steps: + - name: Get tag name + id: tag_name + run: echo "tag=${GITHUB_TAG/refs\/tags\//}" >> $GITHUB_OUTPUT + env: + GITHUB_TAG: ${{ github.ref }} + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: '3.14' + - name: Generate docs snippets and examples + working-directory: frontend/snippets + run: python3 generate.py + - name: Login to GHCR + id: login-ghcr + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + - name: Set up QEMU + uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 + - name: Build dev image + run: | + DOCKER_BUILDKIT=1 docker build -f ./build/package/servers.dockerfile \ + --build-arg SERVER_TARGET=lite \ + --build-arg VERSION=${{steps.tag_name.outputs.tag}} \ + --build-arg GO_BUILD_TAGS=authdisabled \ + --platform linux/arm64 \ + -t hatchet-lite-dev-tmp:arm64 \ + . & + + DOCKER_BUILDKIT=1 docker build -f ./build/package/servers.dockerfile \ + --build-arg SERVER_TARGET=admin \ + --build-arg VERSION=${{steps.tag_name.outputs.tag}} \ + --build-arg GO_BUILD_TAGS=authdisabled \ + --platform linux/arm64 \ + -t hatchet-admin-dev-tmp:arm64 \ + . & + + DOCKER_BUILDKIT=1 docker build -f ./build/package/servers.dockerfile \ + --build-arg SERVER_TARGET=migrate \ + --build-arg VERSION=${{steps.tag_name.outputs.tag}} \ + --platform linux/arm64 \ + -t hatchet-migrate-tmp:arm64 \ + . & + + wait + + DOCKER_BUILDKIT=1 docker build -f ./build/package/lite.dockerfile \ + -t ghcr.io/hatchet-dev/hatchet/hatchet-lite-dev:${{steps.tag_name.outputs.tag}}-arm64 \ + --platform linux/arm64 \ + --build-arg HATCHET_LITE_IMAGE=hatchet-lite-dev-tmp:arm64 \ + --build-arg HATCHET_ADMIN_IMAGE=hatchet-admin-dev-tmp:arm64 \ + --build-arg HATCHET_MIGRATE_IMAGE=hatchet-migrate-tmp:arm64 \ + . + - name: Push to GHCR + run: docker push ghcr.io/hatchet-dev/hatchet/hatchet-lite-dev:${{steps.tag_name.outputs.tag}}-arm64 + combine-hatchet-lite-dev: + name: Combine hatchet-lite-dev + runs-on: ubuntu-latest + needs: + - build-push-hatchet-lite-dev-amd + - build-push-hatchet-lite-dev-arm + steps: + - name: Get tag name + id: tag_name + run: echo "tag=${GITHUB_TAG/refs\/tags\//}" >> $GITHUB_OUTPUT + env: + GITHUB_TAG: ${{ github.ref }} + - name: Login to GHCR + id: login-ghcr + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + - name: Combine dev image + run: | + docker pull ghcr.io/hatchet-dev/hatchet/hatchet-lite-dev:${{steps.tag_name.outputs.tag}}-amd64 + docker pull ghcr.io/hatchet-dev/hatchet/hatchet-lite-dev:${{steps.tag_name.outputs.tag}}-arm64 + docker manifest create ghcr.io/hatchet-dev/hatchet/hatchet-lite-dev:${{steps.tag_name.outputs.tag}} \ + ghcr.io/hatchet-dev/hatchet/hatchet-lite-dev:${{steps.tag_name.outputs.tag}}-amd64 \ + ghcr.io/hatchet-dev/hatchet/hatchet-lite-dev:${{steps.tag_name.outputs.tag}}-arm64 + docker manifest push ghcr.io/hatchet-dev/hatchet/hatchet-lite-dev:${{steps.tag_name.outputs.tag}} build-push-hatchet-dashboard-amd: name: hatchet-dashboard-amd runs-on: ubuntu-latest @@ -630,6 +1016,7 @@ jobs: needs: - build-push-hatchet-dashboard-amd - build-push-hatchet-dashboard-arm + - auth-enabled steps: - name: Get tag name id: tag_name @@ -651,6 +1038,111 @@ jobs: ghcr.io/hatchet-dev/hatchet/hatchet-dashboard:${{steps.tag_name.outputs.tag}}-amd64 \ ghcr.io/hatchet-dev/hatchet/hatchet-dashboard:${{steps.tag_name.outputs.tag}}-arm64 docker manifest push ghcr.io/hatchet-dev/hatchet/hatchet-dashboard:${{steps.tag_name.outputs.tag}} + build-push-hatchet-dashboard-dev-amd: + name: hatchet-dashboard-dev-amd + runs-on: ubuntu-latest + steps: + - name: Get tag name + id: tag_name + run: echo "tag=${GITHUB_TAG/refs\/tags\//}" >> $GITHUB_OUTPUT + env: + GITHUB_TAG: ${{ github.ref }} + - name: Checkout + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: '3.14' + - name: Generate docs snippets and examples + working-directory: frontend/snippets + run: python3 generate.py + - name: Login to GHCR + id: login-ghcr + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + - name: Set up QEMU + uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 + - name: Build dev image + run: | + DOCKER_BUILDKIT=1 docker build -f ./build/package/servers.dockerfile \ + --build-arg SERVER_TARGET=api \ + --build-arg VERSION=${{steps.tag_name.outputs.tag}} \ + --build-arg GO_BUILD_TAGS=authdisabled \ + --platform linux/amd64 \ + -t hatchet-api-dev-tmp:amd64 \ + . + + DOCKER_BUILDKIT=1 docker build -f ./build/package/dashboard.dockerfile \ + -t ghcr.io/hatchet-dev/hatchet/hatchet-dashboard-dev:${{steps.tag_name.outputs.tag}}-amd64 \ + --platform linux/amd64 \ + --build-arg HATCHET_API_IMAGE=hatchet-api-dev-tmp:amd64 \ + . + - name: Push to GHCR + run: docker push ghcr.io/hatchet-dev/hatchet/hatchet-dashboard-dev:${{steps.tag_name.outputs.tag}}-amd64 + build-push-hatchet-dashboard-dev-arm: + name: hatchet-dashboard-dev-arm + runs-on: hatchet-arm64-2 + steps: + - name: Get tag name + id: tag_name + run: echo "tag=${GITHUB_TAG/refs\/tags\//}" >> $GITHUB_OUTPUT + env: + GITHUB_TAG: ${{ github.ref }} + - name: Checkout + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: '3.14' + - name: Generate docs snippets and examples + working-directory: frontend/snippets + run: python3 generate.py + - name: Login to GHCR + id: login-ghcr + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + - name: Set up QEMU + uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 + - name: Build dev image + run: | + DOCKER_BUILDKIT=1 docker build -f ./build/package/servers.dockerfile \ + --build-arg SERVER_TARGET=api \ + --build-arg VERSION=${{steps.tag_name.outputs.tag}} \ + --build-arg GO_BUILD_TAGS=authdisabled \ + --platform linux/arm64 \ + -t hatchet-api-dev-tmp:arm64 \ + . + + DOCKER_BUILDKIT=1 docker build -f ./build/package/dashboard.dockerfile \ + -t ghcr.io/hatchet-dev/hatchet/hatchet-dashboard-dev:${{steps.tag_name.outputs.tag}}-arm64 \ + --platform linux/arm64 \ + --build-arg HATCHET_API_IMAGE=hatchet-api-dev-tmp:arm64 \ + . + - name: Push to GHCR + run: docker push ghcr.io/hatchet-dev/hatchet/hatchet-dashboard-dev:${{steps.tag_name.outputs.tag}}-arm64 + combine-hatchet-dashboard-dev: + name: Combine hatchet-dashboard-dev + runs-on: ubuntu-latest + needs: + - build-push-hatchet-dashboard-dev-amd + - build-push-hatchet-dashboard-dev-arm + steps: + - name: Get tag name + id: tag_name + run: echo "tag=${GITHUB_TAG/refs\/tags\//}" >> $GITHUB_OUTPUT + env: + GITHUB_TAG: ${{ github.ref }} + - name: Login to GHCR + id: login-ghcr + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + - name: Combine dev image + run: | + docker pull ghcr.io/hatchet-dev/hatchet/hatchet-dashboard-dev:${{steps.tag_name.outputs.tag}}-amd64 + docker pull ghcr.io/hatchet-dev/hatchet/hatchet-dashboard-dev:${{steps.tag_name.outputs.tag}}-arm64 + docker manifest create ghcr.io/hatchet-dev/hatchet/hatchet-dashboard-dev:${{steps.tag_name.outputs.tag}} \ + ghcr.io/hatchet-dev/hatchet/hatchet-dashboard-dev:${{steps.tag_name.outputs.tag}}-amd64 \ + ghcr.io/hatchet-dev/hatchet/hatchet-dashboard-dev:${{steps.tag_name.outputs.tag}}-arm64 + docker manifest push ghcr.io/hatchet-dev/hatchet/hatchet-dashboard-dev:${{steps.tag_name.outputs.tag}} build-push-hatchet-loadtest-amd: name: hatchet-loadtest runs-on: ubuntu-latest diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 71cfdbdcd0..de3f5ac09a 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -150,6 +150,18 @@ jobs: ghcr.io/hatchet-dev/hatchet/hatchet-lite:${{steps.tag_name.outputs.tag}}-arm64 docker manifest push ghcr.io/hatchet-dev/hatchet/hatchet-lite:latest + - name: Pull and push dev images + run: | + for img in hatchet-api hatchet-engine hatchet-admin hatchet-lite hatchet-dashboard; do + docker pull ghcr.io/hatchet-dev/hatchet/$img-dev:${{steps.tag_name.outputs.tag}}-arm64 + docker pull ghcr.io/hatchet-dev/hatchet/$img-dev:${{steps.tag_name.outputs.tag}}-amd64 + + docker manifest create ghcr.io/hatchet-dev/hatchet/$img-dev:latest \ + ghcr.io/hatchet-dev/hatchet/$img-dev:${{steps.tag_name.outputs.tag}}-amd64 \ + ghcr.io/hatchet-dev/hatchet/$img-dev:${{steps.tag_name.outputs.tag}}-arm64 + + docker manifest push ghcr.io/hatchet-dev/hatchet/$img-dev:latest + done - name: Pull and push hatchet-dashboard run: | docker pull ghcr.io/hatchet-dev/hatchet/hatchet-dashboard:${{steps.tag_name.outputs.tag}}-arm64 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 774bdff972..88f260e892 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,11 +26,11 @@ repos: exclude: ^examples/ - id: mixed-line-ending args: ["--fix=lf"] - exclude: ^examples/ + exclude: (^examples/|^pkg/authmode/keyset/) - id: end-of-file-fixer - exclude: ^examples/ + exclude: (^examples/|^pkg/authmode/keyset/) - id: trailing-whitespace - exclude: ^examples/ + exclude: (^examples/|^pkg/authmode/keyset/) - id: check-yaml exclude: (^examples/|^cmd/hatchet-cli/cli/templates/) - repo: https://github.com/golangci/golangci-lint diff --git a/api-contracts/openapi/components/schemas/metadata.yaml b/api-contracts/openapi/components/schemas/metadata.yaml index 26f03e1dee..b932aca9df 100644 --- a/api-contracts/openapi/components/schemas/metadata.yaml +++ b/api-contracts/openapi/components/schemas/metadata.yaml @@ -33,6 +33,14 @@ APIMeta: type: boolean description: whether or not a Prometheus federation server is configured (SERVER_PROMETHEUS_SERVER_URL) on this instance example: false + authDisabled: + type: boolean + description: whether or not authentication is disabled (authdisabled build) on this instance + example: false + authDisabledToken: + type: string + description: the embedded worker API token, only set on authdisabled builds + example: eyJhbGciOiJFUzI1NiIs... APIMetaAuth: type: object diff --git a/api/v1/server/authn/authdisabled.go b/api/v1/server/authn/authdisabled.go new file mode 100644 index 0000000000..073a573973 --- /dev/null +++ b/api/v1/server/authn/authdisabled.go @@ -0,0 +1,26 @@ +//go:build authdisabled + +package authn + +import ( + "net/http" + + "github.com/labstack/echo/v4" +) + +func (a *AuthN) authPreflight(c echo.Context) (handled bool, err error) { + ctx := c.Request().Context() + + user, err := a.config.V1.User().GetUserByEmail(ctx, a.config.Seed.AdminEmail) + + if err != nil { + a.l.Error().Ctx(ctx).Err(err).Msg("authdisabled: could not resolve the seeded admin user") + + return true, echo.NewHTTPError(http.StatusInternalServerError, "authdisabled: could not resolve the seeded admin user; ensure the database was seeded (ADMIN_EMAIL/ADMIN_PASSWORD)") + } + + c.Set("user", user) + c.Set("auth_strategy", "authdisabled") + + return true, nil +} diff --git a/api/v1/server/authn/default.go b/api/v1/server/authn/default.go new file mode 100644 index 0000000000..e0f5d4e18c --- /dev/null +++ b/api/v1/server/authn/default.go @@ -0,0 +1,9 @@ +//go:build !authdisabled + +package authn + +import "github.com/labstack/echo/v4" + +func (a *AuthN) authPreflight(c echo.Context) (handled bool, err error) { + return false, nil +} diff --git a/api/v1/server/authn/middleware.go b/api/v1/server/authn/middleware.go index 8c4adc9f5e..15e52145c6 100644 --- a/api/v1/server/authn/middleware.go +++ b/api/v1/server/authn/middleware.go @@ -60,6 +60,13 @@ func (a *AuthN) authenticate(c echo.Context, r *middleware.RouteInfo) error { return a.handleNoAuth(c) } + // authPreflight only returns handled=true in authdisabled builds, where it resolves the + // request as the seeded admin and short-circuits the strategies below. In normal builds it + // is a no-op (returns false), so authentication always proceeds. Do not invert this check. + if handled, err := a.authPreflight(c); handled { + return err + } + var bearerErr error if r.Security.BearerAuth() { diff --git a/api/v1/server/authz/authdisabled.go b/api/v1/server/authz/authdisabled.go new file mode 100644 index 0000000000..2744120b5d --- /dev/null +++ b/api/v1/server/authz/authdisabled.go @@ -0,0 +1,17 @@ +//go:build authdisabled + +package authz + +import ( + "github.com/labstack/echo/v4" + + "github.com/hatchet-dev/hatchet/api/v1/server/middleware" +) + +func (a *AuthZ) authPreflight(c echo.Context, r *middleware.RouteInfo) (handled bool, err error) { + if err := a.authorizeTenantOperations("NOAUTH", r); err != nil { + return true, err + } + + return true, a.validateUserTenantPermissions(c, r) +} diff --git a/api/v1/server/authz/default.go b/api/v1/server/authz/default.go new file mode 100644 index 0000000000..1da37bde82 --- /dev/null +++ b/api/v1/server/authz/default.go @@ -0,0 +1,13 @@ +//go:build !authdisabled + +package authz + +import ( + "github.com/labstack/echo/v4" + + "github.com/hatchet-dev/hatchet/api/v1/server/middleware" +) + +func (a *AuthZ) authPreflight(c echo.Context, r *middleware.RouteInfo) (handled bool, err error) { + return false, nil +} diff --git a/api/v1/server/authz/middleware.go b/api/v1/server/authz/middleware.go index e94d075492..1d2604eded 100644 --- a/api/v1/server/authz/middleware.go +++ b/api/v1/server/authz/middleware.go @@ -48,6 +48,13 @@ func (a *AuthZ) authorize(c echo.Context, r *middleware.RouteInfo) error { return nil } + // authPreflight only returns handled=true in authdisabled builds, where it authorizes the + // request against the NOAUTH role and short-circuits the strategy switch below. In normal + // builds it is a no-op (returns false), so authorization always proceeds. Do not invert this. + if handled, err := a.authPreflight(c, r); handled { + return err + } + var err error switch c.Get("auth_strategy").(string) { @@ -182,7 +189,7 @@ func (a *AuthZ) validateUserTenantPermissions(c echo.Context, r *middleware.Rout c.Set("tenant-member", tenantMember) // authorize tenant operations - if err := a.authorizeTenantOperations(tenantMember.Role, r); err != nil { + if err := a.authorizeTenantOperations(string(tenantMember.Role), r); err != nil { a.l.Debug().Ctx(ctx).Err(err).Msgf("error authorizing tenant operations") return unauthorized @@ -192,14 +199,14 @@ func (a *AuthZ) validateUserTenantPermissions(c echo.Context, r *middleware.Rout return nil } -func (a *AuthZ) authorizeTenantOperations(tenantMemberRole sqlcv1.TenantMemberRole, r *middleware.RouteInfo) error { +func (a *AuthZ) authorizeTenantOperations(roleName string, r *middleware.RouteInfo) error { // if the operation is in the allowed operations, skip the RBAC check this is needed for extensions if rbac.OperationIn(r.OperationID, a.config.Auth.AllowedOperations) { return nil } // at the moment, tenant members are only restricted from creating other tenant users. - if !a.rbac.IsAuthorized(string(tenantMemberRole), r.OperationID) { + if !a.rbac.IsAuthorized(roleName, r.OperationID) { return echo.NewHTTPError(http.StatusUnauthorized, "Not authorized to perform this operation") } diff --git a/api/v1/server/authz/rbac.yaml b/api/v1/server/authz/rbac.yaml index 7313ba0973..481af2e7fb 100644 --- a/api/v1/server/authz/rbac.yaml +++ b/api/v1/server/authz/rbac.yaml @@ -1,6 +1,8 @@ roles: OWNER: inherits: [ADMIN] + NOAUTH: + inherits: [MEMBER] ADMIN: inherits: [MEMBER] permissions: diff --git a/api/v1/server/handlers/metadata/get.go b/api/v1/server/handlers/metadata/get.go index 245c5a4337..97c770763e 100644 --- a/api/v1/server/handlers/metadata/get.go +++ b/api/v1/server/handlers/metadata/get.go @@ -4,6 +4,7 @@ import ( "github.com/labstack/echo/v4" "github.com/hatchet-dev/hatchet/api/v1/server/oas/gen" + "github.com/hatchet-dev/hatchet/pkg/authmode" ) func (u *MetadataService) MetadataGet(ctx echo.Context, request gen.MetadataGetRequestObject) (gen.MetadataGetResponseObject, error) { @@ -36,6 +37,8 @@ func (u *MetadataService) MetadataGet(ctx echo.Context, request gen.MetadataGetR prometheusServerEnabled := u.config.Prometheus.PrometheusServerURL != "" + authDisabled := authmode.IsDisabled + meta := gen.APIMeta{ Auth: &gen.APIMetaAuth{ Schemes: &authTypes, @@ -48,6 +51,13 @@ func (u *MetadataService) MetadataGet(ctx echo.Context, request gen.MetadataGetR AllowChangePassword: &u.config.Runtime.AllowChangePassword, ObservabilityEnabled: &observabilityEnabled, PrometheusServerEnabled: &prometheusServerEnabled, + AuthDisabled: &authDisabled, + } + + if authDisabled { + if embeddedToken := authmode.EmbeddedToken(); embeddedToken != "" { + meta.AuthDisabledToken = &embeddedToken + } } return gen.MetadataGet200JSONResponse(meta), nil diff --git a/api/v1/server/oas/gen/openapi.gen.go b/api/v1/server/oas/gen/openapi.gen.go index d5eb5b8548..33708b3013 100644 --- a/api/v1/server/oas/gen/openapi.gen.go +++ b/api/v1/server/oas/gen/openapi.gen.go @@ -425,6 +425,12 @@ type APIMeta struct { AllowSignup *bool `json:"allowSignup,omitempty"` Auth *APIMetaAuth `json:"auth,omitempty"` + // AuthDisabled whether or not authentication is disabled (authdisabled build) on this instance + AuthDisabled *bool `json:"authDisabled,omitempty"` + + // AuthDisabledToken the embedded worker API token, only set on authdisabled builds + AuthDisabledToken *string `json:"authDisabledToken,omitempty"` + // ObservabilityEnabled whether or not observability (trace collection) is enabled on this instance ObservabilityEnabled *bool `json:"observabilityEnabled,omitempty"` Posthog *APIMetaPosthog `json:"posthog,omitempty"` @@ -17539,369 +17545,370 @@ func (sh *strictHandler) WorkflowVersionGet(ctx echo.Context, workflow openapi_t // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "4/8LAAD//18k+KSphwMA", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/build/package/servers.dockerfile b/build/package/servers.dockerfile index 34fea5c9f1..2423e83b01 100644 --- a/build/package/servers.dockerfile +++ b/build/package/servers.dockerfile @@ -46,6 +46,9 @@ ARG VERSION=v0.1.0-alpha.0 # can be set to "api", "engine", "admin" or "lite" ARG SERVER_TARGET +# optional go build tags, e.g. "authdisabled" for dev images +ARG GO_BUILD_TAGS="" + # check if the target is empty or not set to api, engine, lite, or admin RUN if [ -z "$SERVER_TARGET" ] || [ "$SERVER_TARGET" != "api" ] && [ "$SERVER_TARGET" != "engine" ] && [ "$SERVER_TARGET" != "admin" ] && [ "$SERVER_TARGET" != "lite" ] && [ "$SERVER_TARGET" != "migrate" ]; then \ echo "SERVER_TARGET must be set to 'api', 'engine', 'admin', 'lite', or 'migrate'"; \ @@ -61,7 +64,7 @@ RUN oapi-codegen -config ./api/v1/server/oas/gen/codegen.yaml ./bin/oas/openapi. RUN --mount=type=cache,target=/root/.cache/go-build \ --mount=type=cache,target=$GOPATH/pkg/mod \ - go build -ldflags="-w -s -X 'main.Version=${VERSION}'" -a -o ./bin/hatchet-${SERVER_TARGET} ./cmd/hatchet-${SERVER_TARGET} + go build -tags="${GO_BUILD_TAGS}" -ldflags="-w -s -X 'main.Version=${VERSION}'" -a -o ./bin/hatchet-${SERVER_TARGET} ./cmd/hatchet-${SERVER_TARGET} # Deployment environment # ---------------------- diff --git a/cmd/hatchet-admin/cli/authdisabled.go b/cmd/hatchet-admin/cli/authdisabled.go new file mode 100644 index 0000000000..e64de7ef4c --- /dev/null +++ b/cmd/hatchet-admin/cli/authdisabled.go @@ -0,0 +1,24 @@ +package cli + +import ( + "os" + + "github.com/spf13/cobra" + + "github.com/hatchet-dev/hatchet/pkg/authmode" +) + +var authDisabledCmd = &cobra.Command{ + Use: "authdisabled", + Short: "exit 0 if this is an authdisabled build, 1 otherwise", + Hidden: true, + Run: func(cmd *cobra.Command, args []string) { + if !authmode.IsDisabled { + os.Exit(1) + } + }, +} + +func init() { + rootCmd.AddCommand(authDisabledCmd) +} diff --git a/cmd/hatchet-admin/cli/seed/seed.go b/cmd/hatchet-admin/cli/seed/seed.go index 1a3fd59a1c..3923378704 100644 --- a/cmd/hatchet-admin/cli/seed/seed.go +++ b/cmd/hatchet-admin/cli/seed/seed.go @@ -4,10 +4,12 @@ import ( "context" "errors" "fmt" + "time" "github.com/google/uuid" "github.com/jackc/pgx/v5" + "github.com/hatchet-dev/hatchet/pkg/authmode" "github.com/hatchet-dev/hatchet/pkg/config/database" v1 "github.com/hatchet-dev/hatchet/pkg/repository" "github.com/hatchet-dev/hatchet/pkg/validator" @@ -100,5 +102,47 @@ func SeedDatabase(dc *database.Layer) error { } } + if authmode.IsDisabled { + if err := seedAuthDisabledToken(dc); err != nil { + return err + } + } + return nil } + +func seedAuthDisabledToken(dc *database.Layer) error { + ctx := context.Background() + + if dc.Seed.DefaultTenantID != authmode.EmbeddedTokenTenantID { + return fmt.Errorf("authdisabled mode requires the default tenant ID (%s) to match the embedded token tenant (%s)", dc.Seed.DefaultTenantID, authmode.EmbeddedTokenTenantID) + } + + tokenID, err := uuid.Parse(authmode.EmbeddedTokenID) + if err != nil { + return fmt.Errorf("invalid embedded token ID: %w", err) + } + + if _, getErr := dc.V1.APIToken().GetAPITokenById(ctx, tokenID); getErr == nil { + return nil + } else if !errors.Is(getErr, pgx.ErrNoRows) { + return fmt.Errorf("checking for embedded token: %w", getErr) + } + + tenantID, err := uuid.Parse(authmode.EmbeddedTokenTenantID) + if err != nil { + return fmt.Errorf("invalid embedded token tenant ID: %w", err) + } + + name := "authdisabled-default" + + _, err = dc.V1.APIToken().CreateAPIToken(ctx, &v1.CreateAPITokenOpts{ + ID: tokenID, + ExpiresAt: time.Date(2100, 1, 1, 0, 0, 0, 0, time.UTC), + TenantId: &tenantID, + Name: &name, + Internal: true, + }) + + return err +} diff --git a/cmd/hatchet-admin/cli/token.go b/cmd/hatchet-admin/cli/token.go index 6dcf021222..c30c73a59a 100644 --- a/cmd/hatchet-admin/cli/token.go +++ b/cmd/hatchet-admin/cli/token.go @@ -11,6 +11,7 @@ import ( "github.com/google/uuid" "github.com/spf13/cobra" + "github.com/hatchet-dev/hatchet/pkg/authmode" "github.com/hatchet-dev/hatchet/pkg/config/loader" "github.com/hatchet-dev/hatchet/pkg/config/server" ) @@ -64,14 +65,19 @@ func init() { 90*24*time.Hour, "Expiration duration for the API token", ) - } func runCreateAPIToken(expiresIn time.Duration) error { + // authdisabled builds don't mint tokens; print the single embedded one. + if authmode.IsDisabled { + fmt.Println(authmode.EmbeddedToken()) + return nil + } + // read in the local config configLoader := loader.NewConfigLoader(configDirectory) - cleanup, server, err := configLoader.CreateServerFromConfig("", func(scf *server.ServerConfigFile) { + cleanup, srv, err := configLoader.CreateServerFromConfig("", func(scf *server.ServerConfigFile) { // disable rabbitmq since it's not needed to create the api token scf.MessageQueue.Enabled = false @@ -85,16 +91,16 @@ func runCreateAPIToken(expiresIn time.Duration) error { defer cleanup() // nolint:errcheck - defer server.Disconnect() // nolint:errcheck + defer srv.Disconnect() // nolint:errcheck expiresAt := time.Now().UTC().Add(expiresIn) - tenantId, err := tenantIDForTokenCreate(server.Seed.DefaultTenantID) + tenantId, err := tenantIDForTokenCreate(srv.Seed.DefaultTenantID) if err != nil { return err } - defaultTok, err := server.Auth.JWTManager.GenerateTenantToken(context.Background(), tenantId, tokenName, false, &expiresAt) + defaultTok, err := srv.Auth.JWTManager.GenerateTenantToken(context.Background(), tenantId, tokenName, false, &expiresAt) if err != nil { return err diff --git a/cmd/hatchet-cli/cli/internal/drivers/docker/hatchet_lite.go b/cmd/hatchet-cli/cli/internal/drivers/docker/hatchet_lite.go index e7af069ab1..8256f35e22 100644 --- a/cmd/hatchet-cli/cli/internal/drivers/docker/hatchet_lite.go +++ b/cmd/hatchet-cli/cli/internal/drivers/docker/hatchet_lite.go @@ -52,6 +52,9 @@ const ( startingGrpcPort = 7077 hatchetInternalDashboardPort = 8888 hatchetInternalGrpcPort = 7077 + + defaultLiteImageRepo = "ghcr.io/hatchet-dev/hatchet/hatchet-lite" + devLiteImageRepo = "ghcr.io/hatchet-dev/hatchet/hatchet-lite-dev" ) type HatchetLiteOpts struct { @@ -63,6 +66,7 @@ type HatchetLiteOpts struct { serviceName string overrideDashboardPort int overrideGrpcPort int + imageRepo string imageTag string pullPolicy pullPolicy } @@ -73,11 +77,20 @@ func initDefaultHatchetLiteOpts() *HatchetLiteOpts { hatchetName: defaulthatchetName, projectName: defaultprojectName, serviceName: defaultserviceName, + imageRepo: defaultLiteImageRepo, imageTag: "latest", pullPolicy: pullPolicyAlways, } } +// WithDevImage runs the auth-disabled hatchet-lite-dev image instead of hatchet-lite. +func WithDevImage() HatchetLiteOpt { + return func(o *HatchetLiteOpts) error { + o.imageRepo = devLiteImageRepo + return nil + } +} + type HatchetLiteOpt func(*HatchetLiteOpts) error func WithPostgresName(name string) HatchetLiteOpt { @@ -389,7 +402,7 @@ func (d *DockerDriver) stopPostgresContainer(ctx context.Context, opts *HatchetL } func (d *DockerDriver) startHatchetLiteContainer(ctx context.Context, opts *HatchetLiteOpts, networkId string, dashboardPort, grpcPort int, sharedLabels map[string]string) error { - imageName := "ghcr.io/hatchet-dev/hatchet/hatchet-lite:" + opts.imageTag + imageName := opts.imageRepo + ":" + opts.imageTag containerName := canonicalContainerName(opts.projectName, opts.hatchetName) if err := d.pullImageWithPolicy(ctx, imageName, opts.pullPolicy); err != nil { @@ -417,21 +430,23 @@ func (d *DockerDriver) startHatchetLiteContainer(ctx context.Context, opts *Hatc labels["com.docker.compose.depends_on"] = opts.postgresName labels["com.docker.compose.image"] = imageInspect.ID + env := []string{ + "DATABASE_URL=postgresql://hatchet:hatchet@" + opts.postgresName + ":5432/hatchet?sslmode=disable", + "SERVER_AUTH_COOKIE_DOMAIN=localhost", + "SERVER_AUTH_COOKIE_INSECURE=t", + "SERVER_GRPC_BIND_ADDRESS=0.0.0.0", + "SERVER_GRPC_INSECURE=t", + "SERVER_GRPC_BROADCAST_ADDRESS=localhost:" + fmt.Sprintf("%d", grpcPort), + "SERVER_GRPC_PORT=" + fmt.Sprintf("%d", hatchetInternalGrpcPort), + "SERVER_URL=http://localhost:" + fmt.Sprintf("%d", dashboardPort), + "SERVER_AUTH_SET_EMAIL_VERIFIED=t", + "SERVER_DEFAULT_ENGINE_VERSION=V1", + "SERVER_INTERNAL_CLIENT_INTERNAL_GRPC_BROADCAST_ADDRESS=localhost:" + fmt.Sprintf("%d", hatchetInternalGrpcPort), + } + containerConfig := &container.Config{ - Image: imageName, - Env: []string{ - "DATABASE_URL=postgresql://hatchet:hatchet@" + opts.postgresName + ":5432/hatchet?sslmode=disable", - "SERVER_AUTH_COOKIE_DOMAIN=localhost", - "SERVER_AUTH_COOKIE_INSECURE=t", - "SERVER_GRPC_BIND_ADDRESS=0.0.0.0", - "SERVER_GRPC_INSECURE=t", - "SERVER_GRPC_BROADCAST_ADDRESS=localhost:" + fmt.Sprintf("%d", grpcPort), - "SERVER_GRPC_PORT=" + fmt.Sprintf("%d", hatchetInternalGrpcPort), - "SERVER_URL=http://localhost:" + fmt.Sprintf("%d", dashboardPort), - "SERVER_AUTH_SET_EMAIL_VERIFIED=t", - "SERVER_DEFAULT_ENGINE_VERSION=V1", - "SERVER_INTERNAL_CLIENT_INTERNAL_GRPC_BROADCAST_ADDRESS=localhost:" + fmt.Sprintf("%d", hatchetInternalGrpcPort), - }, + Image: imageName, + Env: env, ExposedPorts: exposedPorts, Labels: labels, Healthcheck: &container.HealthConfig{ diff --git a/cmd/hatchet-cli/cli/server.go b/cmd/hatchet-cli/cli/server.go index 471f8433d9..3f33bb5eee 100644 --- a/cmd/hatchet-cli/cli/server.go +++ b/cmd/hatchet-cli/cli/server.go @@ -40,46 +40,56 @@ var startCmd = &cobra.Command{ hatchet server start --pull-policy never # Only pull images if they are not already available locally - hatchet server start --pull-policy missing`, + hatchet server start --pull-policy missing + + # Start with authentication disabled (runs the hatchet-lite-dev image, local development only) + hatchet server start --disable-auth`, Run: func(cmd *cobra.Command, args []string) { - // Get flag values - dashboardPort, _ := cmd.Flags().GetInt("dashboard-port") - grpcPort, _ := cmd.Flags().GetInt("grpc-port") - projectName, _ := cmd.Flags().GetString("project-name") - profileName, _ := cmd.Flags().GetString("profile") - tag, _ := cmd.Flags().GetString("tag") - pullPolicy, _ := cmd.Flags().GetString("pull-policy") + runServerStart(cmd) + }, +} - opts := []docker.HatchetLiteOpt{} +func runServerStart(cmd *cobra.Command) { + dashboardPort, _ := cmd.Flags().GetInt("dashboard-port") + grpcPort, _ := cmd.Flags().GetInt("grpc-port") + projectName, _ := cmd.Flags().GetString("project-name") + profileName, _ := cmd.Flags().GetString("profile") + tag, _ := cmd.Flags().GetString("tag") + pullPolicy, _ := cmd.Flags().GetString("pull-policy") + disableAuth, _ := cmd.Flags().GetBool("disable-auth") - if dashboardPort != 0 { - opts = append(opts, docker.WithOverrideDashboardPort(dashboardPort)) - } + opts := []docker.HatchetLiteOpt{} - if grpcPort != 0 { - opts = append(opts, docker.WithOverrideGrpcPort(grpcPort)) - } + if disableAuth { + opts = append(opts, docker.WithDevImage()) + } - if projectName != "" { - opts = append(opts, docker.WithProjectName(projectName)) - } + if dashboardPort != 0 { + opts = append(opts, docker.WithOverrideDashboardPort(dashboardPort)) + } - if tag != "" { - opts = append(opts, docker.WithImageTag(tag)) - } + if grpcPort != 0 { + opts = append(opts, docker.WithOverrideGrpcPort(grpcPort)) + } - if pullPolicy != "" { - opts = append(opts, docker.WithPullPolicy(pullPolicy)) - } + if projectName != "" { + opts = append(opts, docker.WithProjectName(projectName)) + } - result, err := startLocalServer(cmd, profileName, opts...) - if err != nil { - cli.Logger.Fatalf("%v", err) - } + if tag != "" { + opts = append(opts, docker.WithImageTag(tag)) + } - // Render styled output - fmt.Println(serverStartedView(result.ProfileName, result.DashboardPort, result.GrpcPort, "")) - }, + if pullPolicy != "" { + opts = append(opts, docker.WithPullPolicy(pullPolicy)) + } + + result, err := startLocalServer(cmd, profileName, opts...) + if err != nil { + cli.Logger.Fatalf("%v", err) + } + + fmt.Println(serverStartedView(result.ProfileName, result.DashboardPort, result.GrpcPort, "")) } var stopCmd = &cobra.Command{ @@ -193,19 +203,23 @@ func serverStartedView(profileName string, dashboardPort, grpcPort int, addition return styles.SuccessBox.Render(strings.Join(lines, "\n")) } +func addServerStartFlags(cmd *cobra.Command) { + cmd.Flags().IntP("dashboard-port", "d", 0, "Port for the Hatchet dashboard (default: auto-detect starting at 8888)") + cmd.Flags().IntP("grpc-port", "g", 0, "Port for the Hatchet gRPC server (default: auto-detect starting at 7077)") + cmd.Flags().StringP("project-name", "p", "", "Docker project name for containers (default: hatchet-cli)") + cmd.Flags().StringP("profile", "n", "local", "Name for the local profile (default: local)") + cmd.Flags().StringP("tag", "t", "latest", `Image tag for the hatchet-lite container (e.g. "v0.83.1")`) + cmd.Flags().String("pull-policy", "always", `Image pull policy: "always", "missing", or "never"`) + cmd.Flags().Bool("disable-auth", false, "Disable authentication by running the hatchet-lite-dev image (local development only)") +} + func init() { rootCmd.AddCommand(serverCmd) serverCmd.AddCommand(startCmd) serverCmd.AddCommand(stopCmd) - // Add flags for server command - startCmd.Flags().IntP("dashboard-port", "d", 0, "Port for the Hatchet dashboard (default: auto-detect starting at 8888)") - startCmd.Flags().IntP("grpc-port", "g", 0, "Port for the Hatchet gRPC server (default: auto-detect starting at 7077)") - startCmd.Flags().StringP("project-name", "p", "", "Docker project name for containers (default: hatchet-cli)") - startCmd.Flags().StringP("profile", "n", "local", "Name for the local profile (default: local)") - startCmd.Flags().StringP("tag", "t", "latest", `Image tag for the hatchet-lite container (e.g. "v0.83.1")`) - startCmd.Flags().String("pull-policy", "always", `Image pull policy: "always", "missing", or "never"`) + addServerStartFlags(startCmd) stopCmd.Flags().StringP("project-name", "p", "", "Docker project name for containers (default: hatchet-cli)") } diff --git a/frontend/app/src/components/layout/app-layout.tsx b/frontend/app/src/components/layout/app-layout.tsx index 993aeec38e..cc2b30cfe6 100644 --- a/frontend/app/src/components/layout/app-layout.tsx +++ b/frontend/app/src/components/layout/app-layout.tsx @@ -5,6 +5,10 @@ type AppLayoutProps = { header: ReactNode; children: ReactNode; footer?: ReactNode; + /** + * Rendered as a full-width strip above the header on every page (e.g. authdisabled warning). + */ + banner?: ReactNode; /** * When true, the content area becomes the scroll container. * When false, content is overflow-hidden (useful when a child layout owns scrolling). @@ -17,20 +21,32 @@ export function AppLayout({ header, children, footer, + banner, contentScroll = true, className, }: AppLayoutProps) { const hasFooter = Boolean(footer); + const hasBanner = Boolean(banner); + const gridRows = cn( + hasBanner && + (hasFooter + ? 'grid-rows-[auto_64px_minmax(0,1fr)_auto]' + : 'grid-rows-[auto_64px_minmax(0,1fr)]'), + !hasBanner && + (hasFooter + ? 'grid-rows-[64px_minmax(0,1fr)_auto]' + : 'grid-rows-[64px_minmax(0,1fr)]'), + ); return (
+ Authentication is disabled, but workers still authenticate over gRPC + with an API token. +
++ This instance ships with a built-in token for the default tenant. + Add it to a CLI profile: +
+
Add a Hatchet CLI profile using an API token.
diff --git a/frontend/app/src/pages/main/v1/overview/index.tsx b/frontend/app/src/pages/main/v1/overview/index.tsx
index dcec1bbef9..2381ee17cd 100644
--- a/frontend/app/src/pages/main/v1/overview/index.tsx
+++ b/frontend/app/src/pages/main/v1/overview/index.tsx
@@ -11,10 +11,12 @@ import {
import { SupportSection } from './components/support-section';
import { TokenSuccessDialog } from './components/token-success-dialog';
import { useAnalytics } from '@/hooks/use-analytics';
+import useAuthDisabled from '@/hooks/use-auth-disabled';
import { useCurrentUser } from '@/hooks/use-current-user';
import { useTenantDetails } from '@/hooks/use-tenant';
import api, { CreateAPITokenRequest, queries } from '@/lib/api';
import { useApiError } from '@/lib/hooks';
+import useApiMeta from '@/pages/auth/hooks/use-api-meta';
import { useMutation, useQuery } from '@tanstack/react-query';
import { useNavigate } from '@tanstack/react-router';
import { useEffect, useMemo, useRef, useState } from 'react';
@@ -28,6 +30,10 @@ const EXPIRES_IN_OPTIONS = {
export default function Overview() {
const { tenant, tenantId } = useTenantDetails();
const { currentUser } = useCurrentUser();
+ const authDisabled = useAuthDisabled();
+ const { meta } = useApiMeta();
+ const authDisabledToken =
+ meta && 'authDisabledToken' in meta ? meta.authDisabledToken : undefined;
const navigate = useNavigate();
const { capture } = useAnalytics();
const [tokenName, setTokenName] = useState('');
@@ -189,6 +195,8 @@ export default function Overview() {
onLanguageChange={setLanguage}
installMethod={installMethod}
onInstallMethodChange={setInstallMethod}
+ authDisabled={authDisabled}
+ authDisabledToken={authDisabledToken}
profileToken={profileToken}
isGeneratingProfileToken={createProfileTokenMutation.isPending}
profileTokenError={profileTokenError}
@@ -220,27 +228,29 @@ export default function Overview() {
}}
/>
-