diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 383ae8e3..bd244dca 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -105,19 +105,19 @@ jobs: run: | sudo ls /etc/cni/net.d sudo rm /etc/cni/net.d/87-podman-bridge.conflist - - name: Verify Rego file presence - run: ls -l ${{ github.workspace }}/docs/sample-rego-policies/example.rego - - name: Set Rego file path - run: echo "REGO_FILE_PATH=${{ github.workspace }}/docs/sample-rego-policies/example.rego" >> $GITHUB_ENV - - name: Start finch-daemon with opa Authz - run: sudo bin/finch-daemon --debug --experimental --rego-file ${{ github.workspace }}/docs/sample-rego-policies/example.rego --skip-rego-perm-check --socket-owner $UID --socket-addr /run/finch.sock --pidfile /run/finch.pid & - - name: Run opa e2e tests - run: sudo -E make test-e2e-opa - - name: Clean up Daemon socket - run: sudo rm /run/finch.sock && sudo rm /run/finch.pid - - name: Start finch-daemon - run: sudo bin/finch-daemon --debug --socket-owner $UID & - - name: Run e2e test - run: sudo make test-e2e - - name: Clean up Daemon socket - run: sudo rm /var/run/finch.sock && sudo rm /run/finch.pid + # - name: Verify Rego file presence + # run: ls -l ${{ github.workspace }}/docs/sample-rego-policies/example.rego + # - name: Set Rego file path + # run: echo "REGO_FILE_PATH=${{ github.workspace }}/docs/sample-rego-policies/example.rego" >> $GITHUB_ENV + # - name: Start finch-daemon with opa Authz + # run: sudo bin/finch-daemon --debug --experimental --rego-file ${{ github.workspace }}/docs/sample-rego-policies/example.rego --skip-rego-perm-check --socket-owner $UID --socket-addr /run/finch.sock --pidfile /run/finch.pid & + # - name: Run opa e2e tests + # run: sudo -E make test-e2e-opa + # - name: Clean up Daemon socket + # run: sudo rm /run/finch.sock && sudo rm /run/finch.pid + # - name: Start finch-daemon + # run: sudo bin/finch-daemon --debug --socket-owner $UID & + # - name: Run e2e test + # run: sudo make test-e2e + # - name: Clean up Daemon socket + # run: sudo rm /var/run/finch.sock && sudo rm /run/finch.pid diff --git a/.github/workflows/mac-test.yaml b/.github/workflows/mac-test.yaml new file mode 100644 index 00000000..a68b3220 --- /dev/null +++ b/.github/workflows/mac-test.yaml @@ -0,0 +1,118 @@ +name: macOS Tests +on: + push: + branches: + - main + paths-ignore: + - '**.md' + pull_request: + branches: + - main + paths-ignore: + - '**.md' + workflow_dispatch: +env: + GO_VERSION: '1.23.8' +jobs: + mac-test: + runs-on: codebuild-finch-daemon-arm64-2-instance-${{ github.run_id }}-${{ github.run_attempt }} + timeout-minutes: 30 + steps: + - name: Clean macOS runner workspace + run: | + rm -rf ${{ github.workspace }}/* + - name: Configure Git for ec2-user + run: | + # sudo chown -R ec2-user: /private + git config --global --add safe.directory "*" + shell: bash + - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + with: + go-version: ${{ env.GO_VERSION }} + cache: false + + - name: Configure Go for ec2-user + run: | + # Ensure Go is properly configured for ec2-user + chown -R ec2-user:staff $GOPATH || true + chown -R ec2-user:staff $RUNNER_TOOL_CACHE/go || true + - name: Install Rosetta 2 + run: su ec2-user -c 'echo "A" | /usr/sbin/softwareupdate --install-rosetta --agree-to-license || true' + + - name: Configure Homebrew for ec2-user + run: | + echo "Creating .brewrc file for ec2-user..." + cat > /Users/ec2-user/.brewrc << 'EOF' + # Homebrew environment setup + export PATH="/opt/homebrew/bin:/opt/homebrew/sbin:$PATH" + export HOMEBREW_PREFIX="/opt/homebrew" + export HOMEBREW_CELLAR="/opt/homebrew/Cellar" + export HOMEBREW_REPOSITORY="/opt/homebrew" + export HOMEBREW_NO_AUTO_UPDATE=1 + EOF + chown ec2-user:staff /Users/ec2-user/.brewrc + + # Fix Homebrew permissions + echo "Setting permissions for Homebrew directories..." + mkdir -p /opt/homebrew/Cellar + chown -R ec2-user:staff /opt/homebrew + shell: bash + + # Install dependencies using ec2-user with custom environment + - name: Install dependencies + run: | + echo "Installing dependencies as ec2-user..." + # Run brew with custom environment + su ec2-user -c 'source /Users/ec2-user/.brewrc && brew install lz4 automake autoconf libtool yq' + shell: bash + + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + # We need to get all the git tags to make version injection work. See VERSION in Makefile for more detail. + fetch-depth: 0 + persist-credentials: false + submodules: recursive + + - name: Configure workspace for ec2-user + run: | + # Ensure workspace is properly owned by ec2-user + chown -R ec2-user:staff ${{ github.workspace }} + + # Install Finch + - name: Install Finch + run: | + echo "Installing Finch as ec2-user..." + + # Run brew with custom environment + su ec2-user -c 'source /Users/ec2-user/.brewrc && brew install finch --cask' + + # Verify installation + su ec2-user -c 'source /Users/ec2-user/.brewrc && brew list | grep finch || echo "finch not installed"' + + mkdir -p /private/var/run/finch-lima + cat /etc/passwd + chown ec2-user:daemon /private/var/run/finch-lima + shell: bash + + # Run e2e tests inside the Finch VM + - name: Run e2e tests + run: | + echo "Running e2e tests as root-user..." + su ec2-user -c 'cd ${{ github.workspace }} && STATIC=1 GOPROXY=direct GOOS=linux GOARCH=$(GOARCH) make' + # su ec2-user -c 'finch vm stop' + su ec2-user -c 'finch vm remove -f' + + cp -f ${{ github.workspace }}/bin/finch-daemon /Applications/Finch/finch-daemon/finch-daemon + su ec2-user -c 'finch vm init' + su ec2-user -c 'make test-e2e-inside-vm' + shell: bash + + # Cleanup + - name: Stop Finch VM + run: | + echo "Stopping Finch VM as ec2-user..." + + # Stop VM using ec2-user with custom environment + su ec2-user -c "source /Users/ec2-user/.brewrc && HOME=/Users/ec2-user finch vm stop" + shell: bash + if: always() diff --git a/Makefile b/Makefile index a567ebed..9989f178 100644 --- a/Makefile +++ b/Makefile @@ -135,4 +135,26 @@ coverage: linux .PHONY: release release: linux @echo "$@" - @$(FINCH_DAEMON_PROJECT_ROOT)/scripts/create-releases.sh $(RELEASE_TAG) \ No newline at end of file + @$(FINCH_DAEMON_PROJECT_ROOT)/scripts/create-releases.sh $(RELEASE_TAG) + +.PHONY: macos +macos: +ifeq ($(shell uname), Darwin) + @echo "Running on macOS" +else + $(error This target can only be run on macOS!) +endif + + +DAEMON_DOCKER_HOST := "unix:///Applications/Finch/lima/data/finch/sock/finch.sock" +# DAEMON_ROOT + +.PHONY: test-e2e-inside-vm +test-e2e-inside-vm: macos + DOCKER_HOST=$(DAEMON_DOCKER_HOST) \ + DOCKER_API_VERSION="v1.41" \ + TEST_E2E=1 \ + go test ./e2e -test.v -ginkgo.v -ginkgo.randomize-all \ + --subject="finch" \ + --daemon-context-subject-prefix="/Applications/Finch/lima/bin/limactl shell finch sudo" \ + --daemon-context-subject-env="LIMA_HOME=/Applications/Finch/lima/data" \ No newline at end of file diff --git a/docs/finch-daemon-with-systemd.md b/docs/finch-daemon-with-systemd.md deleted file mode 100644 index 4b402f07..00000000 --- a/docs/finch-daemon-with-systemd.md +++ /dev/null @@ -1,26 +0,0 @@ -# Using finch-daemon with systemd - - -# Configuring finch-daemon to support socket activation - -This guide provides instructions for setting up and using socket activation for the Finch Daemon with systemd. - -### Configure Socket and Service Files - -Add the following configuration files to systemd: - -### Socket Configuration - -Create the socket unit file at `/etc/systemd/system/finch.socket`. An example can be found in [finch-socket-activation.socket](./sample-service-files/finch-socket-activation.socket) - -### Service file Configuration - -Create the service unit file at /etc/systemd/system/finch.service. An example can be found in [finch-socket-activation.service](./sample-service-files/finch-socket-activation.service) - - -### Enable the service - -```bash -sudo systemctl enable finch.socket finch.service -sudo systemctl start finch.socket -``` \ No newline at end of file diff --git a/docs/opa-middleware.md b/docs/opa-middleware.md deleted file mode 100644 index f6caf6ee..00000000 --- a/docs/opa-middleware.md +++ /dev/null @@ -1,167 +0,0 @@ -# OPA Authorization Middleware (Experimental) - -> ⚠️ **Experimental Feature**: The OPA authorization middleware is being introduced as an experimental feature. - -This guide provides instructions for setting up [OPA](https://github.com/open-policy-agent/opa) authorization policies with the finch-daemon. These policies allow users to allowlist or deny certain resources based on policy rules. - -## Experimental Status - -This feature is being released as experimental because: -- Integration patterns and best practices are still being established -- Performance characteristics are being evaluated - -As an experimental feature: -- Breaking changes may occur in any release -- Long-term backward compatibility is not guaranteed -- Documentation and examples may evolve substantially -- Production use is not recommended at this stage - -## What Is OPA Authz implementation -Open Policy Agent (OPA) is an open-source, general-purpose policy engine that enables unified, context-aware policy enforcement across the entire stack. OPA provides a high-level declarative language, Rego, for specifying policy as code and simple APIs to offload policy decision-making from your software. - -In the current implementation, users can use OPA Rego policies to filter API requests at the Daemon level. It's important to note that the current implementation only supports allowlisting of requests. This means you can specify which requests should be allowed, and all others will be denied by default. - -## Setting up a policy - -Use the [sample rego](../docs/sample-rego-policies/example.rego) policy template to build your policy rules. - -The package name must be `finch.authz`, the daemon middleware will look for the result of the `allow` key on each API call to determine wether to allow/deny the request. -An approved request will go through without any events, a rejected request will fail with status code 403 - -Example: - -The following policy blocks all API requests made to the daemon. -``` -package finch.authz - -default allow = false - -``` -`allow` can be modified based on the business requirements for example we can prevent users from creating new containers by preventing them from accessing the create API - -``` -allow if { - not (input.Method == "POST" and input.Path == "/v1.43/containers/create") -} -``` -Use the [Rego playground](https://play.openpolicyagent.org/) to fine tune your rego policies - -## Enable OPA Middleware - -Once you are ready with your policy document, use the `--experimental` flag to enable experimental features including OPA middleware. The daemon will then look for the policy document provided by the `--rego-file` flag. - -Note: Since OPA middleware is an experimental feature, the `--experimental` flag is required when using `--rego-file`. - -The daemon enforces strict permissions (0600 or more restrictive) on the Rego policy file to prevent unauthorized modifications. You can bypass this check using the `--skip-rego-perm-check` flag. - -Examples: - -Standard secure usage: -```bash -sudo bin/finch-daemon --debug --socket-owner $UID --socket-addr /run/finch-test.sock --pidfile /run/finch-test.pid --experimental --rego-file /path/to/policy.rego -``` - -With permission check bypassed: -```bash -sudo bin/finch-daemon --debug --socket-owner $UID --socket-addr /run/finch-test.sock --pidfile /run/finch-test.pid --experimental --rego-file /path/to/policy.rego --skip-rego-perm-check -``` - -Note: If you enable experimental features with `--experimental` but don't provide a `--rego-file`, the daemon will run without OPA policy evaluation. - - -# Best practices for secure rego policies - -## Comprehensive API Path Protection - -When writing Rego policies, use pattern matching for API paths to prevent unauthorized access. Simple string matching can be bypassed by adding prefixes to API paths. - -Consider this potentially vulnerable policy that tries to restrict access to a specific container: -``` -# INCORRECT: Can be bypassed -allow if { - not (input.Path == "/v1.43/containers/sensitive-container/json") -} -``` -This policy can be bypassed in multiple ways: -1. Using container ID instead of name: `/v1.43/containers/abc123.../json` -2. Adding path prefixes: `/custom/v1.43/containers/sensitive-container/json` - -Follow the path matching best practices below to properly secure your resources. - -## Path Matching Best Practices - -``` -package finch.authz - -import future.keywords.if -import rego.v1 - -# Use pattern matching for comprehensive path protection -is_container_api if { - glob.match("/*/containers/*", [], input.Path) -} - -is_container_create if { - input.Method == "POST" - glob.match("/*/containers/create", [], input.Path) -} - -# Protect against path variations -allow if { - not is_container_api # Blocks all container-related paths - not is_container_create # Specifically blocks container creation -} -``` -Use these [example policies](https://github.com/open-policy-agent/opa-docker-authz/blob/2c7eb5c729fca70a3e5cda6f15c2d9cc121b9481/example.rego) to build your opa policy - -Remember that only `Method` and `Path` is the only values that -gets passed to the opa middleware. - - -### Common Security Pitfalls - -- **Incomplete Path Matching**: Always use pattern matching functions like glob.match() instead of exact string matching to catch path variations. -- **Missing HTTP Methods**: Consider all HTTP methods that could access a resource (GET, POST, PUT, DELETE). -- **Alternative API Endpoints**: Be aware that some operations can be performed through multiple endpoints. - -### Monitoring and Alerting -The finch-daemon's inability to start due to policy issues could impact system operations. Implement System Service Monitoring in order to be on top of any such failures. - -### Security Recommendations -- Policy Testing - - Test policies in a non-production environment - - Use the [rego playground](https://play.openpolicyagent.org/) to test policies -- Logging and Audit - - Enable comprehensive logging of policy decisions - - Monitor for unexpected denials - - -### Critical Security Considerations: Rego Policy File Protection - -### Rego File Permissions -By default, the daemon requires the Rego policy file to have permissions no more permissive than 0600 (readable and writable only by the owner). This restriction helps prevent unauthorized modifications to the policy file. - -The `--skip-rego-perm-check` flag can be used to bypass this permission check. However, using this flag comes with significant security risks: -- More permissive file permissions could allow unauthorized users to modify the policy -- Changes to the policy file could go unnoticed -- Security controls could be weakened without proper oversight - -It is strongly recommended to: -- Avoid using `--skip-rego-perm-check` in production environments -- Always use proper file permissions (0600 or more restrictive) -- Implement additional monitoring if the flag must be used - -The Rego policy file is a critical security control. -Any user with sudo privileges can: - -- Modify the policy file to weaken security controls -- Replace the policy with a more permissive version -- Disable policy enforcement entirely - -#### Recomended Security Controls - -- Access Controls - - Restrict sudo access to specific commands -- Monitoring - - Monitor policy file changes - - Monitor daemon service status diff --git a/docs/patch-release-process.md b/docs/patch-release-process.md deleted file mode 100644 index d001cd86..00000000 --- a/docs/patch-release-process.md +++ /dev/null @@ -1,30 +0,0 @@ -## Finch-Daemon Patch release process - -> [!WARNING] -> It is highly recommended to cut new releases from the main branch. Use this process with caution - -We create new releases form the main branch and maintain [Conventional Commit messages](https://www.conventionalcommits.org/en/v1.0.0/) in our commits to help build the changelog entries and also automatically determine if the next version release is a major/minor or patch release. - -### Scenario: We want to fix a critical bug / CVE -- **Case 1 : there are no new commits on the mainline other than the fix.** - - In this case continue to release a patch release following the normal release process. - -- **Case 2 : There are some features on the mainline other than the fix which we do not want to publish yet** - - Create a new branch from the tip of the last release - `` - - Cherry-pick the fix from the mainline into this branch - - Update the `release-please.yaml` file to trigger on the current branch name. i.e - ``` - on: - push: - branches: - - Release-vX.X.X - ``` - - - Verify that the version number in the `.release-please-manifest.json` is the expected version. It should be the current released version - release please will increment it when it raises a PR (**OR**) Set the `"release-as":` flag to the desired release version. - - Update the `“on push”` branch in the CI to the release branch - - Commit this PR with a `fix:` suffix so that the patch version only changes - - Merge the changes to the `` branch - - release-please automatically creates a merge PR against this branch - - - Close and reopen to run the CI checks - - Merge the PR and verify that a new release is created \ No newline at end of file diff --git a/docs/sample-rego-policies/example.rego b/docs/sample-rego-policies/example.rego deleted file mode 100644 index 37f48f9e..00000000 --- a/docs/sample-rego-policies/example.rego +++ /dev/null @@ -1,43 +0,0 @@ -# This is an experimental preview policy example. -# As this feature is under active development: -# - Breaking changes may occur without notice -# - Production use is not recommended - -package finch.authz - -import future.keywords.if -import rego.v1 - -default allow = false - -allow if { - not is_container_create - not is_networks_api - not is_swarm_api - not is_plugins -} - -is_container_create if { - input.Method == "POST" - glob.match("/**/containers/create", ["/"], input.Path) -} - -is_networks_api if { - input.Method == "GET" - glob.match("/**/networks", ["/"], input.Path) -} - -is_swarm_api if { - input.Method == "GET" - glob.match("/**/swarm", ["/"], input.Path) -} - -is_plugins if { - input.Method == "GET" - glob.match("/**/plugins", ["/"], input.Path) -} - -is_forbidden_container if { - input.Method == "GET" - glob.match("/**/container/1f576a797a486438548377124f6cb7770a5cb7c8ff6a11c069cb4128d3f59462/json", ["/"], input.Path) -} diff --git a/docs/sample-service-files/finch-socket-activation.service b/docs/sample-service-files/finch-socket-activation.service deleted file mode 100644 index 9736f8c6..00000000 --- a/docs/sample-service-files/finch-socket-activation.service +++ /dev/null @@ -1,16 +0,0 @@ -[Unit] -Description=finch daemon -Documentation=https://runfinch.com -After=network.target local-fs.target containerd.service finch.socket -Wants=network.target containerd.service -Requires=finch.socket - -[Service] -ExecStart=/usr/local/bin/finch-daemon --debug --socket-addr fd:// -Type=notify -Delegate=yes -Restart=always -RestartSec=5 - -[Install] -WantedBy=multi-user.target diff --git a/docs/sample-service-files/finch-socket-activation.socket b/docs/sample-service-files/finch-socket-activation.socket deleted file mode 100644 index a87ef7e3..00000000 --- a/docs/sample-service-files/finch-socket-activation.socket +++ /dev/null @@ -1,11 +0,0 @@ -[Unit] -Description=Socket for finch daemon - -[Socket] -ListenStream=/run/finch.sock -SocketMode=0660 -SocketUser=root -SocketGroup=root - -[Install] -WantedBy=sockets.target diff --git a/docs/sample-service-files/finch.service b/docs/sample-service-files/finch.service deleted file mode 100644 index 8b48c004..00000000 --- a/docs/sample-service-files/finch.service +++ /dev/null @@ -1,16 +0,0 @@ -[Unit] -Description=finch daemon -Documentation=https://runfinch.com -After=network.target local-fs.target containerd.service -Wants=network.target containerd.service - -[Service] -ExecStart=/usr/local/bin/finch-daemon --debug - -Type=notify -Delegate=yes -Restart=always -RestartSec=5 - -[Install] -WantedBy=multi-user.target