In a previous post, we described how repositories were migrated from Bitbucket Cloud to GitHub Cloud while preserving full Git history, including branches, tags, and submodule references. That migration established a solid foundation, but moving repositories alone is only part of a successful transition.

This article continues the journey by focusing on securely updating project dependencies after the move to GitHub. Ensuring that dependencies are handled correctly is essential for keeping builds stable and CI pipelines running reliably throughout the migration process.

Once repositories are in place, all project-related dependencies must be reviewed and updated where necessary. In this case, the projects rely on Yocto, an open-source collaboration project that provides tools, templates, and methods for building custom Linux-based Linux systems for embedded products across different hardware architectures.

github

During the dependency review, we identified that several Yocto recipes were relying on the Git fetcher over HTTPS, with hardcoded credentials embedded directly in the recipe. These credentials appeared both in the SRC_URI variable and in custom functions responsible for cloning submodules.

A simplified example of such a recipe looked like this:

DESCRIPTION = "Example Service for Embedded System"
LICENSE = "MIT"

BRANCH = "master"
SRC_URI = "git://bitbucket.org/organization/example_service.git;protocol=https;user=<username>:<hardcoded_token>;branch=${BRANCH}"
SRCREV = "<commit_hash>"

do_configure_prepend() {
    cd ${WORKDIR}/git

    # Clone submodule 1 using HTTPS and hardcoded token
    rm -rf submodule1
    git clone https://<username>:<hardcoded_token>@bitbucket.org/organization/submodule1.git submodule1
    cd submodule1
    git checkout <version>

    # Clone submodule 2 using HTTPS and hardcoded token
    cd ${WORKDIR}/git
    rm -rf submodule2
    git clone https://<username>:<hardcoded_token>@bitbucket.org/organization/submodule2.git submodule2
}

This approach introduces multiple risks:

For a long-term, scalable CI/CD setup, this method is neither secure nor reliable.

To improve security and ensure seamless automation, all affected recipes were updated to use SSH-based authentication instead of HTTPS. This removes the need for embedded tokens and aligns better with automated build environments.

The updated recipe looks like this:

DESCRIPTION = "Example Service for Embedded System"
LICENSE = "MIT"

BRANCH = "master"
SRC_URI = "git://git@github.com/organization/example_service.git;protocol=ssh;branch=${BRANCH}"
SRCREV = "<commit_hash>"

do_configure_prepend() {
    cd ${WORKDIR}/git

    # Clone submodule 1 securely over SSH
    rm -rf submodule1
    git clone git@github.com:organization/submodule1.git submodule1
    cd submodule1
    git checkout <version>

    # Clone submodule 2 securely over SSH
    cd ${WORKDIR}/git
    rm -rf submodule2
    git clone git@github.com:organization/submodule2.git submodule2
}

With this change:

To support SSH-based dependency fetching, the CI environment must have access to a private SSH key. When BitBake executes these recipes, it uses this key to authenticate against GitHub for all referenced repositories and submodules.

The corresponding public key must be associated with a GitHub account that has access to all required repositories.

It’s important to note that a single SSH key is sufficient. As long as the linked GitHub account has the correct permissions, the same key can be used across multiple repositories and submodules.

To avoid SSH verification issues, GitHub should also be configured as a trusted host:

ssh-keyscan github.com >> $HOME/.ssh/known_hosts

In this setup, the build runs inside a Docker container, so the container must mount the host’s .ssh directory. This allows BitBake to access the SSH key during fetch operations.

Below is an example GitHub Actions pipeline used in this scenario:

name: Build Embedded Project

on:
  push:
    branches:
      - master
      - 'release/*'
      - 'feature/*'
  workflow_dispatch:

permissions:
  id-token: write
  contents: read

env:
  DOWNLOAD_DIR: /opt/build_dir
  BRANCH: ${{ github.ref_name }}

jobs:
  build:
    runs-on: [self-hosted, linux, x64]

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
      
      - name: Set up SSH key
        run: |
          mkdir -p $HOME/.ssh
          echo "${{ secrets.SSH_PRIVATE_KEY }}" > $HOME/.ssh/id_ed25519
          chmod 600 $HOME/.ssh/id_ed25519
          ssh-keyscan github.com >> $HOME/.ssh/known_hosts
          ssh -T git@github.com || true

      - name: Prepare build environment
        run: |
          make -f docker.mk cleanup DOWNLOAD_DIR=$DOWNLOAD_DIR

      - name: Build project
        run: |
          make -f docker.mk DOWNLOAD_DIR=$DOWNLOAD_DIR

      - name: Upload build artifacts
        uses: actions/upload-artifact@v4
        with:
          name: project-build
          path: |
            $DOWNLOAD_DIR/output/*.tar.gz
            $DOWNLOAD_DIR/output/*.ipk

In the Set up SSH key step, the pipeline retrieves the private key from GitHub Secrets, configures GitHub as a trusted host, and verifies authentication. This ensures that all repositories and submodules defined in the BitBake recipes can be accessed securely during the build.

This concludes Part 2 of our migration journey series.

In the next post, we’ll focus on modernizing CI/CD by migrating existing Bash and Jenkins pipelines to GitHub Actions, bringing all automation under a single, standardized workflow. Stay tuned for Part 3!

Check out more of our blog posts here.