diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index ee74795..acd28a4 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -2,7 +2,7 @@ name: Release to Production on: push: - branches: [ main ] + branches: [main] env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -45,7 +45,6 @@ jobs: if: ${{ steps.version.outputs.VERSION != ''}} uses: docker/setup-buildx-action@v2 - - name: Login to GHCR if: ${{ steps.version.outputs.VERSION != ''}} uses: docker/login-action@v1 @@ -56,6 +55,7 @@ jobs: - name: Build and Push Docker Image if: ${{ steps.version.outputs.VERSION != ''}} + id: build-and-push uses: docker/build-push-action@v2 with: context: . @@ -63,67 +63,98 @@ jobs: platforms: linux/amd64,linux/arm64 push: true tags: ghcr.io/${{ github.repository }}:${{steps.version.outputs.VERSION}} - + + - uses: sigstore/cosign-installer@v3.3.0 + + - name: Image Signing + run: | + cosign sign --yes \ + -a "repo=${{ github.repository }}" \ + -a "workflow=${{ github.workflow }}" \ + -a "ref=${{ github.sha }}" \ + -a "owner=Spectro Cloud" \ + --key env://COSIGN_PRIVATE_KEY --recursive "${TAGS}@${DIGEST}" + env: + TAGS: ghcr.io/${{ github.repository }}:${{steps.dependencies.outputs.VERSION}} + COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} + COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} + DIGEST: ${{ steps.build-and-push.outputs.digest }} + docker-reverse-proxy: name: "Docker w/Proxy image" runs-on: ubuntu-latest outputs: VERSION: ${{ steps.version.outputs.version }} steps: - - id: checkout - name: Checkout Repository - uses: actions/checkout@v3 - - - name: Setup Nodejs - uses: actions/setup-node@v3 - with: - node-version: 18 - - - name: Install dependencies - run: npm ci - - - id: version - name: Determine Release Version - run: | - npm install @semantic-release/exec -D - npm install @semantic-release/changelog -D - npm install @semantic-release/git -D - npx semantic-release --dry-run - cat VERSION.env - source VERSION.env - echo "::set-output name=version::$VERSION" - - - - name: Set up QEMU - if: ${{ steps.version.outputs.VERSION != ''}} - uses: docker/setup-qemu-action@v2 - - - name: Set up Docker Buildx - if: ${{ steps.version.outputs.VERSION != ''}} - uses: docker/setup-buildx-action@v2 - - - - name: Login to GHCR - if: ${{ steps.version.outputs.VERSION != ''}} - uses: docker/login-action@v1 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and Push Docker Image - if: ${{ steps.version.outputs.VERSION != ''}} - uses: docker/build-push-action@v2 - with: - context: . - file: Dockerfile.Caddy - platforms: linux/amd64,linux/arm64 - push: true - tags: ghcr.io/${{ github.repository }}:${{steps.version.outputs.VERSION}}-proxy + - id: checkout + name: Checkout Repository + uses: actions/checkout@v3 + + - name: Setup Nodejs + uses: actions/setup-node@v3 + with: + node-version: 18 + + - name: Install dependencies + run: npm ci + + - id: version + name: Determine Release Version + run: | + npm install @semantic-release/exec -D + npm install @semantic-release/changelog -D + npm install @semantic-release/git -D + npx semantic-release --dry-run + cat VERSION.env + source VERSION.env + echo "::set-output name=version::$VERSION" + + - name: Set up QEMU + if: ${{ steps.version.outputs.VERSION != ''}} + uses: docker/setup-qemu-action@v2 + + - name: Set up Docker Buildx + if: ${{ steps.version.outputs.VERSION != ''}} + uses: docker/setup-buildx-action@v2 + + - name: Login to GHCR + if: ${{ steps.version.outputs.VERSION != ''}} + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and Push Docker Image + if: ${{ steps.version.outputs.VERSION != ''}} + id: build-and-push + uses: docker/build-push-action@v2 + with: + context: . + file: Dockerfile.Caddy + platforms: linux/amd64,linux/arm64 + push: true + tags: ghcr.io/${{ github.repository }}:${{steps.version.outputs.VERSION}}-proxy + + - uses: sigstore/cosign-installer@v3.3.0 + + - name: Image Signing + run: | + cosign sign --yes \ + -a "repo=${{ github.repository }}" \ + -a "workflow=${{ github.workflow }}" \ + -a "ref=${{ github.sha }}" \ + -a "owner=Spectro Cloud" \ + --key env://COSIGN_PRIVATE_KEY --recursive "${TAGS}@${DIGEST}" + env: + TAGS: ghcr.io/${{ github.repository }}:${{steps.dependencies.outputs.VERSION}} + COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} + COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} + DIGEST: ${{ steps.build-and-push.outputs.digest }} release: name: "Release" - needs: [docker,docker-reverse-proxy] + needs: [docker, docker-reverse-proxy] runs-on: ubuntu-latest steps: - id: checkout @@ -141,4 +172,4 @@ jobs: - name: "release" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: npx semantic-release \ No newline at end of file + run: npx semantic-release diff --git a/README.md b/README.md index d876fae..c117ae2 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,11 @@ Hello Universe is a demo application intended for learning about [Palette](https drawing

-# Run App +## Start App Get started with Hello Universe by choosing between two deployment approaches; docker or a non-docker-based approach. -## Docker +### Docker Hello Universe is available as a Docker image. To run Hello Universe issue the following commands: @@ -20,7 +20,8 @@ docker pull ghcr.io/spectrocloud/hello-universe:1.1.0 docker run -p 8080:8080 ghcr.io/spectrocloud/hello-universe:1.1.0 ``` -## Non-Docker +### Non-Docker + To run locally without Docker: ``` @@ -29,26 +30,23 @@ npm ci npm run start ``` -## Environment Variables +### Environment Variables Hello Universe accepts the following environment variables: -| Variable | Description | Default | -|-------------|----------------------------------------------------|-----------| -| API_URI | The fully qualified hostname and port of the API server. In a reverse proxy setting this can be the application loadbalancer. | `""` | -| API_VERSION | The API version number. | `1` | -| SVC_URI | The URI to the service API, such as the internal Kubernetes container hostname of the API service. |`""`| -| TOKEN | The API authorization token. This is only used if the API is configured for authorization. |`""`| - +| Variable | Description | Default | +| ----------- | ----------------------------------------------------------------------------------------------------------------------------- | ------- | +| API_URI | The fully qualified hostname and port of the API server. In a reverse proxy setting this can be the application loadbalancer. | `""` | +| API_VERSION | The API version number. | `1` | +| SVC_URI | The URI to the service API, such as the internal Kubernetes container hostname of the API service. | `""` | +| TOKEN | The API authorization token. This is only used if the API is configured for authorization. | `""` | +### Connecting to API Server -## Connecting to API Server - -Hello Universe's capabilities can be expanded if connected to the [Hello Universe API](https://github.com/spectrocloud/hello-universe-api). +Hello Universe's capabilities can be expanded if connected to the [Hello Universe API](https://github.com/spectrocloud/hello-universe-api). To connect Hello Universe to the API server, provide the API server's fully qualified hostname and port as an environment variable value. Be aware that the API server requires an available Postgres database. Checkout [Hello Universe DB](https://github.com/spectrocloud/hello-universe-db) for a dockerized Postgres instance ready for integration with the Hello Universe API. - ```shell API_URI=http://localhost:3000 ``` @@ -61,15 +59,18 @@ docker run -p 8080:8080 -e API_URI=http://localhost:3000 ghcr.io/spectrocloud/he ### Reverse Proxy -A Docker container with a reverse proxy is available. The reverse proxy is usefull for scenarios when you need to deploy the +A Docker container with a reverse proxy is available. The reverse proxy is usefull for scenarios when you need to deploy the hello universe application into a Kubernetes cluster or similar architectures and need the UI to route requests internal to the hosting platform. An example of such behavior is needing to to reach a private API inside the Kubernetes cluster. **The reverse proxy expects the API to be listening on port `3000`.** ```shell docker run -p 8080:8080 -p 3000:3000 -e SVC_URI="http://myprivate.api.address.example:3000" -e API_URI="http://myloadbalancer.example:3000" ghcr.io/spectrocloud/hello-universe:1.1.0-proxy ``` -# Development +## Image Verification +We sign our images through [Cosign](https://docs.sigstore.dev/signing/quickstart/). Review the [Image Verification](./docs/image-verification.md) page to learn more. + +## Development Create an environment file `.env` file and add the following values: @@ -80,21 +81,18 @@ REACT_APP_API_VERSION=1 The `.env` file is how you point to the local development API server. Otherwise, local browser storage is used. - Use the [`docker-compose.yml`](./docker-compose.yml) to start the required services. ```shell make start-services ``` - Next, start the local development server ```shell make start ``` - To stop the docker containers, use the following command. ```shell @@ -102,17 +100,23 @@ To stop the docker containers, use the following command. ``` ## Clean + To remove the build folder use the command `make clean` ## Build + To build the hosting assets use the command `make build` + ### Development Server + To start the local development server without a proxy use the command `make start`. ### Server w/o Reverse Proxy + To start the Caddy server without a reverse proxy use the command `make start-prod`. ### Server w/o Reverse Proxy + To start the Caddy server with a reverse proxy use the command `make start-proxy`. ## Dependencies diff --git a/docs/image-verification.md b/docs/image-verification.md new file mode 100644 index 0000000..9680d77 --- /dev/null +++ b/docs/image-verification.md @@ -0,0 +1,81 @@ +# Image Verification + +The Tutorials container image is signed using [Sigstore's](https://sigstore.dev/) Cosign. The container image is signed using a cryptographic key pair that is private and stored internally. The public key is available in the official Spectro Cloud documentation repository at [**static/cosign.pub**](https://raw.githubusercontent.com/spectrocloud/librarium/master/static/cosign.pub). Use the public key to verify the authenticity of the container image. You can learn more about the container image signing process by reviewing the [Signing Containers](https://docs.sigstore.dev/signing/signing_with_containers) documentation page. + +> [!NOTE] +> Cosign generates a key pair that uses the ECDSA-P256 algorithm for the signature and SHA256 for hashes. The keys are stored in PEM-encoded PKCS8 format. + +Use the following command to verify the authenticity of the container image. Replace the image tag with the version you want to verify. + +```shell +cosign verify --key https://raw.githubusercontent.com/spectrocloud/librarium/master/static/cosign.pub \ +docker pull ghcr.io/spectrocloud/hello-universe:1.1.1 +``` + +If the container image is valid, the following output is displayed. The example output is formatted using `jq` to improve readability. + +```shell hideClipboard +Verification for ghcr.io/spectrocloud/hello-universe:1.1.1 -- +The following checks were performed on each of these signatures: + - The cosign claims were validated + - Existence of the claims in the transparency log was verified offline + - The signatures were verified against the specified public key +[ + { + "critical": { + "identity": { + "docker-reference": "ghcr.io/spectrocloud/hello-universe:1.1.1" + }, + "image": { + "docker-manifest-digest": "sha256:285a95a8594883b3748138460182142f5a1b74f80761e2fecb1b86d3c9b9d191" + }, + "type": "cosign container image signature" + }, + "optional": { + "Bundle": { + "SignedEntryTimestamp": "MEYCIQCZ6FZzNB5wA9+W/lF57jx0qTaszZhg5FxJiBmgIFxPVwIhANnoQQ5gqjr1h93LCq1Td8BohqrxxIvfrXTnT1tYR4i7", + "Payload": { + "body": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI0MzU0MzFjNjY1Y2Y2ZGZjYzM0NzI2YWRkNjAzMDVjYjZlMzhlNjVkZmJlMWQ0NWU2ZGVkM2IzNzg3NTYwY2MxIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJUUM0TFFxYVFDclhOc0VzdkI0ZE84bmtZSWg0L3o5UzdScGVEdUZnUDJwbDJ3SWdOdEJsNElDaHBmT3RnVDBlNW5QTmRMYWt4RTJHcnFFK0tjV1JXSGZPTnpnPSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCUVZVSk1TVU1nUzBWWkxTMHRMUzBLVFVacmQwVjNXVWhMYjFwSmVtb3dRMEZSV1VsTGIxcEplbW93UkVGUlkwUlJaMEZGV1VoeVl6SlhTVVV6WVhCTFRHMWplR3hHUmtoNVZsRkRVVnBYYUFveUsyRnNOVmN2Vmsxc1VISXpkVFJGV2k5V0wwZFBRbTAySzFrNVowWXpWWE16ZEhkMVpWaFpaMlJaWlVadk5XODNRbFZ1TnpCTlVGQjNQVDBLTFMwdExTMUZUa1FnVUZWQ1RFbERJRXRGV1MwdExTMHRDZz09In19fX0=", + "integratedTime": 1702758491, + "logIndex": 57230483, + "logID": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d" + } + }, + "owner": "Spectro Cloud", + "ref": "e597f70be238369ce4f0e5778492a155e23fec17", + "repo": "spectrocloud/hello-universe", + "workflow": "Release" + } + } +] +``` + +> [!CAUTION] +> Do not use the container image if the authenticity cannot be verified. Verify you downloaded the correct public key and that the container image is from `ghcr.io/spectrocloud/tutorials`. + +If the container image is not valid, an error is displayed. The following example shows an error when the container image is not valid. + +```shell hideClipboard +cosign verify --key https://raw.githubusercontent.com/spectrocloud/librarium/master/static/cosign.pub \ +ghcr.io/spectrocloud/hello-universe:1.1.0 +``` + +```shell hideClipboard +Error: no matching signatures: error verifying bundle: comparing public key PEMs, expected -----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEheVfGYrVn2mIUQ4cxMJ6x09oXf82 +zFEMG++p4q8Mf+y2gp7Ae4oUaXk6Q9V7aVjjltRVN6SQcoSASxf2H2EpgA== +-----END PUBLIC KEY----- +, got -----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEYHrc2WIE3apKLmcxlFFHyVQCQZWh +2+al5W/VMlPr3u4EZ/V/GOBm6+Y9gF3Us3twueXYgdYeFo5o7BUn70MPPw== +-----END PUBLIC KEY----- + +main.go:69: error during command execution: no matching signatures: error verifying bundle: comparing public key PEMs, expected -----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEheVfGYrVn2mIUQ4cxMJ6x09oXf82 +zFEMG++p4q8Mf+y2gp7Ae4oUaXk6Q9V7aVjjltRVN6SQcoSASxf2H2EpgA== +-----END PUBLIC KEY----- +, got -----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEYHrc2WIE3apKLmcxlFFHyVQCQZWh +2+al5W/VMlPr3u4EZ/V/GOBm6+Y9gF3Us3twueXYgdYeFo5o7BUn70MPPw== +-----END PUBLIC KEY----- +```