Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: populate metadata.component.externalReferences VCS and build-system from common CI environment variables #1344

Open
jeremylong opened this issue Dec 19, 2024 · 0 comments
Labels
enhancement New feature or request

Comments

@jeremylong
Copy link

Is your feature request related to a problem? Please describe.

To improve traceability of a given SBOM, it would be fantastic if the metadata.component.externalReferences were populated for the vcs and build-system URLs. These can be obtained via well-known environment variables in many CI systems.

Describe the solution you'd like

The _helper.ts could be extended with code to detect the build-system and VCS from common CI environment variables:

import { execSync } from 'child_process'

...

export function detectBuildUrl (): string | undefined {
  if (process.env.GITHUB_ACTIONS === 'true') {
    return `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`
  }
  if (process.env.GITLAB_CI === 'true' && isNonNullable(process.env.CI_JOB_URL)) {
    return process.env.CI_JOB_URL
  }
  if (isNonNullable(process.env.CIRCLECI)) {
    return process.env.CIRCLE_BUILD_URL
  }
  if (isNonNullable(process.env.JENKINS_URL)) {
    return process.env.BUILD_URL
  }
  if (isNonNullable(process.env.TF_BUILD)) {
    return `${process.env.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI}${process.env.SYSTEM_TEAMPROJECT}/_build/results?buildId=${process.env.BUILD_BUILDID}`
  }
  if (isNonNullable(process.env.TRAVIS)) {
    return process.env.TRAVIS_BUILD_WEB_URL
  }
  if (isNonNullable(process.env.BITBUCKET_BUILD_NUMBER)) {
    return process.env.BITBUCKET_GIT_HTTP_ORIGIN
  }
  if (isNonNullable(process.env.CODEBUILD_PUBLIC_BUILD_URL)) {
    return process.env.CODEBUILD_PUBLIC_BUILD_URL
  }
  if (isNonNullable(process.env.DRONE_BUILD_LINK)) {
    return process.env.DRONE_BUILD_LINK
  }
  return undefined
}

export function detectSourceUrl(): string | undefined {
  try {
    const hasGit = execSync('which git', { stdio: 'ignore' })
    if (hasGit !== null && hasGit.length > 0) {
      const gitUrl = execSync('git remote get-url origin 2>/dev/null', { encoding: 'utf8' }).trim()
      if (gitUrl !== null && gitUrl !== '') {
        if (gitUrl.startsWith('git@') && gitUrl.endsWith('.git')) {
          return gitUrl.replace(':', '/').replace('git@', 'https://')
        }
        return gitUrl
      }
    }
  } catch (error) {
    // Fall through to environment checks if git commands fail
  }

  if (isNonNullable(process.env.GITHUB_REPOSITORY)) {
    return `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}`
  }
  if (isNonNullable(process.env.GITLAB_CI)) {
    return process.env.CI_REPOSITORY_URL
  }
  if (isNonNullable(process.env.CIRCLECI)) {
    return process.env.CIRCLE_REPOSITORY_URL
  }
  if (isNonNullable(process.env.JENKINS_URL)) {
    return process.env.GIT_URL
  }
  if (isNonNullable(process.env.TF_BUILD)) {
    return process.env.BUILD_REPOSITORY_URI
  }
  if (isNonNullable(process.env.BITBUCKET_GIT_HTTP_ORIGIN)) {
    return process.env.BITBUCKET_GIT_HTTP_ORIGIN
  }
  if (isNonNullable(process.env.BITBUCKET_GIT_SSH_ORIGIN)) {
    return process.env.BITBUCKET_GIT_SSH_ORIGIN
  }
  if (isNonNullable(process.env.CODEBUILD_BUILD_ID)) {
    return process.env.CODEBUILD_SOURCE_REPO_URL
  }
  if (isNonNullable(process.env.DRONE_REPO_LINK)) {
    return process.env.DRONE_REPO_LINK
  }
  return undefined
}

Then plugin.ts#makeRootComponent could be updated to end with the following instead of just return builder.makeComponent(thisPackageJson):

const component = builder.makeComponent(thisPackageJson)

    if (component !== undefined) {
      const sourceUrl = detectBuildUrl()
      if (sourceUrl !== undefined) {
        component.externalReferences.add({
          type: CDX.Enums.ExternalReferenceType.VCS,
          url: sourceUrl,
          hashes: new CDX.Models.HashDictionary(),
          comment: '',
          compare: () => 0
        })
      }

      const buildUrl = detectSourceUrl()
      if (buildUrl !== undefined) {
        component.externalReferences.add({
          type: CDX.Enums.ExternalReferenceType.BuildSystem,
          url: buildUrl,
          hashes: new CDX.Models.HashDictionary(),
          comment: '',
          compare: () => 0
        })
      }
    }

    return component

Describe alternatives you've considered

An alternative would be to allow these values to be specified as configuration options.

Additional context

I'm more than happy to put in a PR with these changes - but I know the code is in flux with the pending 4.0.0 release and some of this may change when the project upgrades to the [email protected].

@jeremylong jeremylong added the enhancement New feature or request label Dec 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant