Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
openSUSE:Leap:16.0:FactoryCandidates
kbom
kbom-0.3.1.obscpio
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File kbom-0.3.1.obscpio of Package kbom
07070100000000000041ED000000000000000000000002671FABC100000000000000000000000000000000000000000000001300000000kbom-0.3.1/.github07070100000001000041ED000000000000000000000002671FABC100000000000000000000000000000000000000000000002200000000kbom-0.3.1/.github/ISSUE_TEMPLATE07070100000002000081A4000000000000000000000001671FABC1000000AE000000000000000000000000000000000000002D00000000kbom-0.3.1/.github/ISSUE_TEMPLATE/config.ymlblank_issues_enabled: true contact_links: - name: Ask a question url: https://github.com/rad-security/kbom/discussions about: Please ask and answer questions here. 07070100000003000041ED000000000000000000000002671FABC100000000000000000000000000000000000000000000001D00000000kbom-0.3.1/.github/workflows07070100000004000081A4000000000000000000000001671FABC100000716000000000000000000000000000000000000002700000000kbom-0.3.1/.github/workflows/build.ymlname: build on: pull_request: jobs: version: runs-on: ubuntu-latest if: startsWith(github.head_ref, 'renovate') == false steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Detect Version for Docker id: docker-version run: echo "VERSION=$(SEP="-" scripts/version)" >> $GITHUB_OUTPUT - name: Detect Version id: version run: echo "VERSION=$(scripts/version)" >> $GITHUB_OUTPUT outputs: docker-version: ${{ steps.docker-version.outputs.VERSION }} version: ${{ steps.version.outputs.VERSION }} build: runs-on: ubuntu-latest needs: - version steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: # Force version to solve cache restore issue: https://github.com/actions/setup-go/issues/506 go-version: 1.23.2 check-latest: true # https://github.com/actions/setup-go#check-latest-version cache: true # https://github.com/actions/setup-go#caching-dependency-files-and-build-outputs - name: Build run: go build -race ./... - name: Install GoReleaser uses: goreleaser/goreleaser-action@v5 with: version: latest install-only: true - name: Snapshot run: make snapshot env: GORELEASER_CURRENT_TAG: ${{ needs.version.outputs.docker-version }} - name: Grype scan id: scan uses: anchore/scan-action@v5 with: path: "." fail-build: true severity-cutoff: medium output-format: sarif - name: Upload SARIF report uses: github/codeql-action/upload-sarif@v3 with: sarif_file: ${{ steps.scan.outputs.sarif }} 07070100000005000081A4000000000000000000000001671FABC100000084000000000000000000000000000000000000002500000000kbom-0.3.1/.github/workflows/dco.ymlname: DCO Check on: pull_request: jobs: check: runs-on: ubuntu-latest steps: - uses: KineticCafe/actions-dco@v1 07070100000006000081A4000000000000000000000001671FABC100000269000000000000000000000000000000000000002F00000000kbom-0.3.1/.github/workflows/golangci-lint.ymlname: golangci-lint on: pull_request: permissions: contents: read jobs: golangci: name: lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v5 with: go-version: '1.23' check-latest: true # https://github.com/actions/setup-go#check-latest-version cache: true # https://github.com/actions/setup-go#caching-dependency-files-and-build-outputs - name: golangci-lint uses: golangci/golangci-lint-action@v6 with: version: v1.60.3 args: --timeout=5m 07070100000007000081A4000000000000000000000001671FABC100000938000000000000000000000000000000000000002900000000kbom-0.3.1/.github/workflows/release.ymlname: release on: push: tags: - "v*" workflow_call: secrets: PERSONAL_ACCESS_TOKEN: required: true PUBLIC_GCR_JSON_KEY: required: true permissions: contents: write # needed to write releases id-token: write # needed for keyless signing jobs: version: runs-on: ubuntu-latest if: startsWith(github.head_ref, 'renovate') == false steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Detect Version for Docker id: docker-version run: echo "VERSION=$(SEP="-" scripts/version)" >> $GITHUB_OUTPUT - name: Detect Version id: version run: echo "VERSION=$(scripts/version)" >> $GITHUB_OUTPUT outputs: docker-version: ${{ steps.docker-version.outputs.VERSION }} version: ${{ steps.version.outputs.VERSION }} goreleaser: timeout-minutes: 90 runs-on: ubuntu-latest needs: - version env: SUMMARY: ${{ needs.version.outputs.docker-version }} VERSION: ${{ needs.version.outputs.version }} steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Go uses: actions/setup-go@v5 with: go-version: '1.23' check-latest: true # https://github.com/actions/setup-go#check-latest-version cache: true # https://github.com/actions/setup-go#caching-dependency-files-and-build-outputs - uses: sigstore/cosign-installer@v3 - uses: anchore/sbom-action/download-syft@v0 - name: Login to GCR uses: docker/login-action@v3 with: registry: us.gcr.io username: _json_key password: ${{ secrets.PUBLIC_GCR_JSON_KEY }} - name: Install GoReleaser uses: goreleaser/goreleaser-action@v5 with: version: latest install-only: true - name: Generate SBOM uses: CycloneDX/gh-gomod-generate-sbom@v2 with: args: mod -licenses -json -output bom.json version: ^v1 - name: Release if: startsWith(github.ref , 'refs/tags/v') == true run: make release env: GITHUB_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} 07070100000008000081A4000000000000000000000001671FABC10000031E000000000000000000000000000000000000002600000000kbom-0.3.1/.github/workflows/scan.ymlname: scan on: workflow_dispatch: push: branches: [ main ] pull_request: branches: [ main ] schedule: - cron: '18 10 * * 3' permissions: contents: read jobs: scan-codeql: runs-on: ubuntu-latest permissions: security-events: write steps: - name: Checkout repository uses: actions/checkout@v4 - name: Setup Go uses: actions/setup-go@v5 with: go-version: 1.23.x cache-dependency-path: | **/go.sum **/go.mod - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: go - name: Autobuild uses: github/codeql-action/autobuild@v3 - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 07070100000009000081A4000000000000000000000001671FABC1000001B9000000000000000000000000000000000000002600000000kbom-0.3.1/.github/workflows/test.ymlname: test on: pull_request: jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version: '1.23' check-latest: true # https://github.com/actions/setup-go#check-latest-version cache: true # https://github.com/actions/setup-go#caching-dependency-files-and-build-outputs - name: Test run: go test -race ./... 0707010000000A000081A4000000000000000000000001671FABC100000090000000000000000000000000000000000000001600000000kbom-0.3.1/.gitignore# Directories vendor release archive # Files .envrc # KBOM Binaries /kbom* # BOM file bom.json # Coverage coverage.out coverage_report.html 0707010000000B000081A4000000000000000000000001671FABC100000510000000000000000000000000000000000000001900000000kbom-0.3.1/.golangci.ymllinters-settings: dupl: threshold: 110 funlen: lines: 100 statements: 50 goconst: min-len: 2 min-occurrences: 3 gocritic: enabled-tags: - diagnostic - experimental - opinionated - performance - style disabled-checks: - dupImport # https://github.com/go-critic/go-critic/issues/845 - ifElseChain - octalLiteral - whyNoLint gocyclo: min-complexity: 15 goimports: local-prefixes: github.com/rad-security golint: min-confidence: 0 lll: line-length: 140 maligned: suggest-new: true misspell: locale: US linters: # please, do not use `enable-all`: it's deprecated and will be removed soon. # inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint disable-all: true enable: - bodyclose - dogsled - dupl - errcheck - exportloopref - funlen - goconst - gocritic - gocyclo - gofmt - goimports - goprintffuncname - gosec - gosimple - govet - ineffassign - lll - misspell - nakedret - noctx - nolintlint - staticcheck - stylecheck - typecheck - unconvert - unparam - unused - whitespace issues: exclude-rules: - path: _test\.go linters: - funlen run: timeout: 2m 0707010000000C000081A4000000000000000000000001671FABC100000C28000000000000000000000000000000000000001B00000000kbom-0.3.1/.goreleaser.ymldist: release env: - PACKAGE_NAME=github.com/{{.Env.GITHUB_ORG}}/{{.Env.APP_NAME}} - CFG_PACKAGE_NAME=github.com/{{.Env.GITHUB_ORG}}/{{.Env.APP_NAME}}/internal/config before: hooks: - go mod tidy release: extra_files: - glob: ./bom.json github: name: kbom owner: rad-security discussion_category_name: Announcements builds: - binary: kbom goos: - darwin - linux - windows goarch: - "386" - amd64 - arm - arm64 goarm: - "7" ignore: - goos: darwin goarch: "386" ldflags: - -X "{{.Env.CFG_PACKAGE_NAME}}.AppName=kbom" - -X "{{.Env.CFG_PACKAGE_NAME}}.AppVersion={{.Env.VERSION}}" - -X "{{.Env.CFG_PACKAGE_NAME}}.BuildTime={{.Env.BUILD_TIME}}" - -X "{{.Env.CFG_PACKAGE_NAME}}.LastCommitUser={{.Env.LAST_COMMIT_USER}}" - -X "{{.Env.CFG_PACKAGE_NAME}}.LastCommitHash={{.Env.LAST_COMMIT_HASH}}" - -X "{{.Env.CFG_PACKAGE_NAME}}.LastCommitTime={{.Env.LAST_COMMIT_TIME}}" main: ./ env: - CGO_ENABLED=0 # create a source tarball # https://goreleaser.com/customization/source/ source: enabled: true # creates SBOMs of all archives and the source tarball using syft # https://goreleaser.com/customization/sbom sboms: - artifacts: archive - id: source # Two different sbom configurations need two different IDs artifacts: source # signs the checksum file # all files (including the sboms) are included in the checksum, so we don't need to sign each one if we don't want to # https://goreleaser.com/customization/sign signs: - cmd: cosign env: - COSIGN_EXPERIMENTAL=1 certificate: '${artifact}.pem' args: - sign-blob - '--output-certificate=${certificate}' - '--output-signature=${signature}' - '${artifact}' - "--yes" # needed on cosign 2.0.0+ artifacts: checksum output: true brews: - repository: owner: rad-security name: homebrew-kbom homepage: "https://github.com/rad-security/kbom" description: "The Kubernetes Bill of Materials (KBOM) standard provides insight into container orchestration tools widely used across the industry." license: "Apache 2" test: | system "#{bin}/kbom", "version" dockers: - goos: linux goarch: amd64 dockerfile: build/package/Dockerfile.gorelease image_templates: - us.gcr.io/{{.Env.GCR_ORG}}/{{.Env.APP_NAME}}:{{- if .IsSnapshot -}}{{ .Env.VERSION }}{{- else -}}{{ .Tag }}{{- end -}} build_flag_templates: - "--pull" - "--label=org.opencontainers.image.created={{.Date}}" - "--label=org.opencontainers.image.title={{.ProjectName}}" - "--label=org.opencontainers.image.revision={{.FullCommit}}" - "--label=org.opencontainers.image.version={{.Version}}" - "--label=org.opencontainers.image.source=https://{{.Env.PACKAGE_NAME}}" - "--platform=linux/amd64" checksum: name_template: "checksums.txt" snapshot: name_template: "snapshot-{{ .ShortCommit }}-{{ .Timestamp }}" changelog: sort: asc filters: exclude: - "^docs:" - "^test:" 0707010000000D000081A4000000000000000000000001671FABC100000288000000000000000000000000000000000000002300000000kbom-0.3.1/.pre-commit-config.yamlrepos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.3.0 hooks: - id: check-byte-order-marker - id: check-case-conflict - id: check-merge-conflict - id: check-symlinks - id: detect-aws-credentials args: ['--allow-missing-credentials'] - id: detect-private-key - id: end-of-file-fixer - id: mixed-line-ending - id: trailing-whitespace - repo: https://github.com/dnephin/pre-commit-golang.git rev: v0.5.0 hooks: - id: go-fmt - id: go-imports - id: go-mod-tidy - id: go-build - id: go-unit-tests - id: golangci-lint 0707010000000E000081A4000000000000000000000001671FABC10000001C000000000000000000000000000000000000001600000000kbom-0.3.1/CODEOWNERS* @rad-security/engineering 0707010000000F000081A4000000000000000000000001671FABC100000A00000000000000000000000000000000000000001B00000000kbom-0.3.1/CONTRIBUTING.md# Contributing KBOM is [Apache 2.0 licensed](https://github.com/rad-security/kbom/blob/main/LICENSE) and accepts contributions via GitHub pull requests. This document outlines some of the conventions on to make it easier to get your contribution accepted. We gratefully welcome improvements to issues and documentation as well as to code. ## Certificate of Origin By contributing to this project you agree to the Developer Certificate of Origin (DCO). This document was created by the Linux Kernel community and is a simple statement that you, as a contributor, have the legal right to make the contribution. We require all commits to be signed. By signing off with your signature, you certify that you wrote the patch or otherwise have the right to contribute the material by the rules of the [DCO](DCO): `Signed-off-by: Firstname Lastname <firstname.lastname@example.com>` The signature must contain your real name (sorry, no pseudonyms or anonymous contributions) If your `user.name` and `user.email` are configured in your Git config, you can sign your commit automatically with `git commit -s`. ## Communications To discuss ideas and specifications we use [GitHub Discussions](https://github.com/rad-security/kbom/discussions). ## How to run the KBOM generator in local environment Prerequisites: * go >= 1.20 * kind * golangci-lint Initialise repo: ```bash make initialise ``` To generate your first KBOM file we need to have access to a Kubernetes cluster. If you don't have any you could create your local cluster with `Kind`. Create kind cluster(optional): ```bash kind create cluster --name kbom-test ``` Build `kbom` binary: ```bash make build ``` Generate your first `kbom` file: ```bash ./kbom generate ``` ## Acceptance policy These things will make a PR more likely to be accepted: * a well-described requirement * tests for new code * tests for old code! * new code and tests follow the conventions in old code and tests * a good commit message (see below) * all code must abide [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments) * names should abide [What's in a name](https://talks.golang.org/2014/names.slide#1) * code must build on both Linux and Darwin, via plain `go build` * code should have appropriate test coverage and tests should be written to work with `go test` In general, we will merge a PR once one maintainer has endorsed it. For substantial changes, more people may become involved, and you might get asked to resubmit the PR or divide the changes into more than one PR. 07070100000010000081A4000000000000000000000001671FABC100002C5D000000000000000000000000000000000000001300000000kbom-0.3.1/LICENSE Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 07070100000011000081A4000000000000000000000001671FABC10000080C000000000000000000000000000000000000001400000000kbom-0.3.1/MakefileAPP_NAME := kbom GCR_ORG := ksoc-public GITHUB_ORG := rad-security GIT_REPO ?= github.com/$(GITHUB_ORG)/$(APP_NAME) VERSION := $(shell SEP="-" bash scripts/version) BUILD_TIME ?= $(shell date -u '+%Y-%m-%d %H:%M:%S') LAST_COMMIT_USER ?= $(shell git log -1 --format='%cn <%ce>') LAST_COMMIT_HASH ?= $(shell git log -1 --format=%H) LAST_COMMIT_TIME ?= $(shell git log -1 --format=%cd --date=format:'%Y-%m-%d %H:%M:%S') export APP_NAME export GCR_ORG export GITHUB_ORG export VERSION export BUILD_TIME export LAST_COMMIT_USER export LAST_COMMIT_HASH export LAST_COMMIT_TIME .PHONY: initialise initialise: ## Initialises the project, set ups git hooks pre-commit install .PHONY: release release: ## Builds a release goreleaser release --clean --timeout 90m .PHONY: semtag-% semtag-%: ## Creates a new tag using semtag semtag final -s $* .PHONY: snapshot snapshot: ## Builds a snapshot release GORELEASER_CURRENT_TAG=$(GORELEASER_CURRENT_TAG) \ goreleaser build --snapshot --clean --single-target --timeout 90m .PHONY: docker_push_all docker_push_all: ## Pushes all docker images docker push $$(docker images -a | grep $(APP_NAME) | awk '{ print $$1 ":" $$2 }') .PHONY: build build: ## Builds kbom binary CGO_ENABLED=0 \ go build \ -v \ -ldflags "-s -w \ -X '$(GIT_REPO)/internal/config.AppName=$(APP_NAME)' \ -X '$(GIT_REPO)/internal/config.AppVersion=$(VERSION)' \ -X '$(GIT_REPO)/internal/config.BuildTime=$(BUILD_TIME)' \ -X '$(GIT_REPO)/internal/config.LastCommitUser=$(LAST_COMMIT_USER)' \ -X '$(GIT_REPO)/internal/config.LastCommitHash=$(LAST_COMMIT_HASH)' \ -X '$(GIT_REPO)/internal/config.LastCommitTime=$(LAST_COMMIT_TIME)'" \ -o $(APP_NAME) . .PHONY: test test: ## Runs unit tests go test -coverprofile coverage.out -v --race ./... go tool cover -html=coverage.out -o coverage_report.html .PHONY: help help: ## Displays this help screen @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n\nTargets:\n"} /^[a-zA-Z_-]+:.*?##/ \ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 }' $(MAKEFILE_LIST) 07070100000012000081A4000000000000000000000001671FABC10000094E000000000000000000000000000000000000001500000000kbom-0.3.1/README.md# KBOM - Kubernetes Bill of Materials ![GitHub release (latest by date)](https://img.shields.io/github/v/release/rad-security/kbom) ![Hex.pm](https://img.shields.io/hexpm/l/apa) [![Go Report Card](https://goreportcard.com/badge/github.com/rad-security/kbom)](https://goreportcard.com/report/github.com/rad-security/kbom) [![OpenSSF Best Practices](https://bestpractices.coreinfrastructure.org/projects/7273/badge)](https://bestpractices.coreinfrastructure.org/projects/7273) The Kubernetes Bill of Materials (KBOM) standard provides insight into container orchestration tools widely used across the industry. As a first draft, we have created a rough specification which should fall in line with other Bill of Materials (BOM) standards. The KBOM project provides an initial specification in JSON and has been constructed for extensibilty across various cloud service providers (CSPs) as well as DIY Kubernetes. ## Getting Started ### Installation ```sh brew install rad-security/homebrew-kbom/kbom ``` or ```sh make build ``` ### Usage `KBOM generate` generates a KBOM file for your Kubernetes cluster ```sh kbom generate [flags] ``` Optional flags include: ```plain Flags: -f, --format string Format (json, yaml, cyclonedx-json, cyclonedx-xml) (default "json") -h, --help help for generate -p, --out-path string Path to write KBOM file to. Works only with --output=file (default ".") -o, --output string Output (stdout, file) (default "stdout") --short Short - only include metadata, nodes, images and resources counters ``` ## Schema The high level object model can be found [here](docs/schema.md). ## Supported Kubernetes Versions We have tested *kbom* with all versions newer than *v1.19*, and can confirm that it is fully compatible with each of these versions. This means that you can use our tool with confidence, knowing that it has been thoroughly tested with. ## Supported Cloud Providers We have tested our tool with all of the main cloud providers, including `Azure`, `AWS`, and `Google Cloud`. Of course it's possible to generate `kbom` file for any K8s cluster, but please have in mind that in some cases not all metadata entries will be set. ## Contributing KBOM is Apache 2.0 licensed and accepts contributions via GitHub pull requests. See the [CONTRIBUTING](CONTRIBUTING.md) file for details. 07070100000013000081A4000000000000000000000001671FABC1000001BA000000000000000000000000000000000000001700000000kbom-0.3.1/SECURITY.md# Security Policy ## Supported Versions We currently support the latest release for security patching and will deploy forward releases. For example if there is a vulnerability in release `v0.1.0` we will fix that release in version `v0.1.1-fix` or `v0.1.1` ## Reporting a Vulnerability If you are aware of a vulnverability please feel free to disclose it responsibly [here](https://github.com/rad-security/kbom/security/advisories/new). 07070100000014000041ED000000000000000000000002671FABC100000000000000000000000000000000000000000000001100000000kbom-0.3.1/build07070100000015000041ED000000000000000000000002671FABC100000000000000000000000000000000000000000000001900000000kbom-0.3.1/build/package07070100000016000081A4000000000000000000000001671FABC100000052000000000000000000000000000000000000002E00000000kbom-0.3.1/build/package/Dockerfile.goreleaseFROM alpine:3.20.0 RUN mkdir /app COPY kbom /app/kbom ENTRYPOINT ["/app/kbom"] 07070100000017000041ED000000000000000000000002671FABC100000000000000000000000000000000000000000000000F00000000kbom-0.3.1/cmd07070100000018000081A4000000000000000000000001671FABC100001CB3000000000000000000000000000000000000001D00000000kbom-0.3.1/cmd/cyclonexdx.gopackage cmd import ( "fmt" "hash/fnv" "slices" "time" "github.com/CycloneDX/cyclonedx-go" "github.com/google/uuid" "github.com/mitchellh/hashstructure/v2" "github.com/rad-security/kbom/internal/model" ) const ( CdxPrefix = "cdx:" RADPrefix = "rad:kbom:" K8sComponentType = "k8s:component:type" K8sComponentName = "k8s:component:name" K8sComponentVersion = "k8s:component:version" ClusterType = "cluster" NodeType = "node" ContainerType = "container" ) func transformToCycloneDXBOM(kbom *model.KBOM) *cyclonedx.BOM { //nolint:funlen cdxBOM := cyclonedx.NewBOM() cdxBOM.SerialNumber = uuid.New().URN() cdxBOM.Metadata = &cyclonedx.Metadata{ Timestamp: time.Now().Format(time.RFC3339), Tools: &[]cyclonedx.Tool{ { Vendor: kbom.GeneratedBy.Vendor, Name: kbom.GeneratedBy.Name, Version: kbom.GeneratedBy.Version, }, }, } components := []cyclonedx.Component{} dependencies := []cyclonedx.Dependency{} clusterProperties := []cyclonedx.Property{ { Name: CdxPrefix + K8sComponentType, Value: ClusterType, }, { Name: CdxPrefix + "k8s:component:name", Value: kbom.Cluster.Name, }, { Name: RADPrefix + "k8s:cluster:nodes", Value: fmt.Sprintf("%d", kbom.Cluster.NodesCount), }, } if kbom.Cluster.Location.Name != "" && kbom.Cluster.Location.Name != "unknown" { clusterProperties = append(clusterProperties, cyclonedx.Property{ Name: RADPrefix + "k8s:cluster:location:name", Value: kbom.Cluster.Location.Name, }) } if kbom.Cluster.Location.Region != "" { clusterProperties = append(clusterProperties, cyclonedx.Property{ Name: RADPrefix + "k8s:cluster:location:region", Value: kbom.Cluster.Location.Region, }) } if kbom.Cluster.Location.Zone != "" { clusterProperties = append(clusterProperties, cyclonedx.Property{ Name: RADPrefix + "k8s:cluster:location:zone", Value: kbom.Cluster.Location.Zone, }) } clusterComponent := cyclonedx.Component{ BOMRef: kbom.Cluster.BOMRef(), Type: cyclonedx.ComponentTypePlatform, Name: kbom.Cluster.BOMName(), Version: kbom.Cluster.K8sVersion, Properties: &clusterProperties, } cdxBOM.Metadata.Component = &clusterComponent clusterDependencies := make(map[string]string) for i := range kbom.Cluster.Nodes { n := kbom.Cluster.Nodes[i] bomRef := id(n) components = append(components, cyclonedx.Component{ BOMRef: bomRef, Type: cyclonedx.ComponentTypePlatform, Name: n.Name, Properties: &[]cyclonedx.Property{ { Name: CdxPrefix + K8sComponentType, Value: NodeType, }, { Name: CdxPrefix + K8sComponentName, Value: n.Name, }, { Name: RADPrefix + "k8s:node:osImage", Value: n.OsImage, }, { Name: RADPrefix + "k8s:node:arch", Value: n.Architecture, }, { Name: RADPrefix + "k8s:node:kernel", Value: n.KernelVersion, }, { Name: RADPrefix + "k8s:node:bootId", Value: n.BootID, }, { Name: RADPrefix + "k8s:node:type", Value: n.Type, }, { Name: RADPrefix + "k8s:node:operatingSystem", Value: n.OperatingSystem, }, { Name: RADPrefix + "k8s:node:machineId", Value: n.MachineID, }, { Name: RADPrefix + "k8s:node:hostname", Value: n.Hostname, }, { Name: RADPrefix + "k8s:node:containerRuntimeVersion", Value: n.ContainerRuntimeVersion, }, { Name: RADPrefix + "k8s:node:kubeletVersion", Value: n.KubeletVersion, }, { Name: RADPrefix + "k8s:node:kubeProxyVersion", Value: n.KubeProxyVersion, }, { Name: RADPrefix + "k8s:node:capacity:cpu", Value: n.Capacity.CPU, }, { Name: RADPrefix + "k8s:node:capacity:memory", Value: n.Capacity.Memory, }, { Name: RADPrefix + "k8s:node:capacity:pods", Value: n.Capacity.Pods, }, { Name: RADPrefix + "k8s:node:capacity:ephemeralStorage", Value: n.Capacity.EphemeralStorage, }, { Name: RADPrefix + "k8s:node:allocatable:cpu", Value: n.Allocatable.CPU, }, { Name: RADPrefix + "k8s:node:allocatable:memory", Value: n.Allocatable.Memory, }, { Name: RADPrefix + "k8s:node:allocatable:pods", Value: n.Allocatable.Pods, }, { Name: RADPrefix + "k8s:node:allocatable:ephemeralStorage", Value: n.Allocatable.EphemeralStorage, }, }, }) clusterDependencies[bomRef] = bomRef } for _, img := range kbom.Cluster.Components.Images { bomRef := img.PkgID() container := cyclonedx.Component{ BOMRef: bomRef, Type: cyclonedx.ComponentTypeContainer, Name: img.Name, Version: img.Digest, PackageURL: bomRef, Properties: &[]cyclonedx.Property{ { Name: CdxPrefix + K8sComponentType, Value: ContainerType, }, { Name: CdxPrefix + K8sComponentName, Value: img.Name, }, { Name: RADPrefix + "pkg:type", Value: "oci", }, { Name: RADPrefix + "pkg:name", Value: img.Name, }, { Name: RADPrefix + "pkg:version", Value: img.Version, }, { Name: RADPrefix + "pkg:digest", Value: img.Digest, }, }, } components = append(components, container) if img.ControlPlane { clusterDependencies[bomRef] = bomRef } } for _, resList := range kbom.Cluster.Components.Resources { for _, res := range resList.Resources { properties := []cyclonedx.Property{ { Name: CdxPrefix + K8sComponentType, Value: resList.Kind, }, { Name: CdxPrefix + K8sComponentName, Value: res.Name, }, { Name: RADPrefix + "k8s:component:apiVersion", Value: resList.APIVersion, }, } if version, ok := res.AdditionalProperties["version"]; ok { properties = append(properties, cyclonedx.Property{ Name: RADPrefix + K8sComponentVersion, Value: version, }) } if resList.Namespaced { properties = append(properties, cyclonedx.Property{ Name: RADPrefix + "k8s:component:namespace", Value: res.Namespace, }) } resource := cyclonedx.Component{ BOMRef: id(res), Type: cyclonedx.ComponentTypeApplication, // TODO: this is not perfect but we don't have a better option Name: res.Name, Version: res.APIVersion, Properties: &properties, } components = append(components, resource) } } clusterDependenciesArr := make([]string, 0) for _, dep := range clusterDependencies { clusterDependenciesArr = append(clusterDependenciesArr, dep) } slices.Sort(clusterDependenciesArr) dependencies = append(dependencies, cyclonedx.Dependency{ Ref: clusterComponent.BOMRef, Dependencies: &clusterDependenciesArr, }, ) cdxBOM.Components = &components cdxBOM.Dependencies = &dependencies return cdxBOM } func id(obj interface{}) string { f, err := hashstructure.Hash(obj, hashstructure.FormatV2, &hashstructure.HashOptions{ ZeroNil: true, SlicesAsSets: true, Hasher: fnv.New64(), }) // this should never happen, but if it does, we don't want to crash - use empty string if err != nil { fmt.Printf("failed to hash object: %v", err) return "" } return fmt.Sprintf("%016x", f) } 07070100000019000081A4000000000000000000000001671FABC1000003BA000000000000000000000000000000000000001900000000kbom-0.3.1/cmd/format.gopackage cmd import "fmt" type Format struct { Name string FileExtension string } var JSONFormat = Format{ Name: "json", FileExtension: "json", } var YAMLFormat = Format{ Name: "yaml", FileExtension: "yaml", } var CycloneDXJsonFormat = Format{ Name: "cyclonedx-json", FileExtension: "json", } var CycloneDXXMLFormat = Format{ Name: "cyclonedx-xml", FileExtension: "xml", } func formatNames() []string { return []string{ JSONFormat.Name, YAMLFormat.Name, CycloneDXJsonFormat.Name, CycloneDXXMLFormat.Name, } } func formatFromName(name string) (Format, error) { switch name { case JSONFormat.Name: return JSONFormat, nil case YAMLFormat.Name: return YAMLFormat, nil case CycloneDXJsonFormat.Name: return CycloneDXJsonFormat, nil case CycloneDXXMLFormat.Name: return CycloneDXXMLFormat, nil default: return Format{}, fmt.Errorf("format %q is not supported", name) } } 0707010000001A000081A4000000000000000000000001671FABC100001137000000000000000000000000000000000000001B00000000kbom-0.3.1/cmd/generate.gopackage cmd import ( "context" "encoding/json" "fmt" "io" "os" "path" "strings" "time" "github.com/CycloneDX/cyclonedx-go" "github.com/google/uuid" "github.com/spf13/cobra" "gopkg.in/yaml.v3" "github.com/rad-security/kbom/internal/config" "github.com/rad-security/kbom/internal/kube" "github.com/rad-security/kbom/internal/model" "github.com/rad-security/kbom/internal/utils" ) const ( Company = "RAD Security" BOMFormat = "rad" SpecVersion = "0.3" StdOutput = "stdout" FileOutput = "file" ) var ( short bool output string format string outPath string generatedAt = time.Now() kbomID = uuid.New().String() ) var GenerateCmd = &cobra.Command{ Use: "generate", Short: "Generate KBOM for the provided K8s cluster", RunE: runGenerate, } func init() { GenerateCmd.Flags().BoolVar(&short, "short", false, "Short - only include metadata, nodes, images and resources counters") GenerateCmd.Flags().StringVarP(&output, "output", "o", StdOutput, "Output (stdout, file)") GenerateCmd.Flags().StringVarP(&format, "format", "f", JSONFormat.Name, fmt.Sprintf("Format (%s)", strings.Join(formatNames(), ", "))) GenerateCmd.Flags().StringVarP(&outPath, "out-path", "p", ".", "Path to write KBOM file to. Works only with --output=file") utils.BindFlags(GenerateCmd) } func runGenerate(cmd *cobra.Command, _ []string) error { k8sClient, err := kube.NewClient(k8sContext) if err != nil { return err } return generateKBOM(k8sClient) } func generateKBOM(k8sClient kube.K8sClient) error { parsedFormat, err := formatFromName(format) if err != nil { return err } ctx := context.Background() k8sVersion, caCertDigest, err := k8sClient.Metadata(ctx) if err != nil { return err } clusterName, err := k8sClient.ClusterName(ctx) if err != nil { return err } full := !short nodes, err := k8sClient.AllNodes(ctx, full) if err != nil { return err } loc, err := k8sClient.Location(ctx) if err != nil { return err } allImages, err := k8sClient.AllImages(ctx) if err != nil { return err } resources, err := k8sClient.AllResources(ctx, full) if err != nil { return err } kbom := model.KBOM{ ID: kbomID, BOMFormat: BOMFormat, SpecVersion: SpecVersion, GeneratedAt: generatedAt, GeneratedBy: model.Tool{ Vendor: Company, BuildTime: config.BuildTime, Name: config.AppName, Version: config.AppVersion, Commit: config.LastCommitHash, CommitTime: config.LastCommitTime, }, Cluster: model.Cluster{ Name: clusterName, Location: loc, CNIVersion: "", // TODO: get CNI version K8sVersion: k8sVersion, CACertDigest: caCertDigest, NodesCount: len(nodes), Nodes: nodes, Components: model.Components{ Images: allImages, Resources: resources, }, }, } if err := printKBOM(&kbom, parsedFormat); err != nil { return err } return nil } func printKBOM(kbom *model.KBOM, f Format) error { writer, err := getWriter(kbom, f) if err != nil { return err } defer writer.Close() switch format { case JSONFormat.Name: enc := json.NewEncoder(writer) enc.SetIndent("", " ") return enc.Encode(kbom) case YAMLFormat.Name: enc := yaml.NewEncoder(writer) enc.SetIndent(2) return enc.Encode(kbom) case CycloneDXJsonFormat.Name: cyclonexKbom := transformToCycloneDXBOM(kbom) enc := cyclonedx.NewBOMEncoder(writer, cyclonedx.BOMFileFormatJSON) enc.SetPretty(true) enc.SetEscapeHTML(false) return enc.Encode(cyclonexKbom) case CycloneDXXMLFormat.Name: cyclonexKbom := transformToCycloneDXBOM(kbom) enc := cyclonedx.NewBOMEncoder(writer, cyclonedx.BOMFileFormatXML) enc.SetPretty(true) enc.SetEscapeHTML(false) return enc.Encode(cyclonexKbom) default: return fmt.Errorf("format %q is not supported", format) } } func getWriter(kbom *model.KBOM, format Format) (io.WriteCloser, error) { switch output { case StdOutput: return out, nil case FileOutput: formattedTime := kbom.GeneratedAt.Format("2006-01-02-15-04-05") key := kbom.ID[:8] if len(kbom.Cluster.CACertDigest) > 8 { key = kbom.Cluster.CACertDigest[:8] } f, err := os.Create(path.Join(outPath, fmt.Sprintf("kbom-%s-%s.%s", key, formattedTime, format.FileExtension))) if err != nil { return nil, err } return f, nil default: return nil, fmt.Errorf("output %q is not supported", output) } } 0707010000001B000081A4000000000000000000000001671FABC1000038C2000000000000000000000000000000000000002000000000kbom-0.3.1/cmd/generate_test.gopackage cmd import ( "bytes" "context" "fmt" "os" "testing" "time" "github.com/stretchr/testify/assert" "github.com/rad-security/kbom/internal/kube" "github.com/rad-security/kbom/internal/model" ) func TestGenerateKBOM(t *testing.T) { type testCase struct { name string // mocks clientMock kube.K8sClient idMock string timeMock string // flags output string format string expectedOut string expectedErr error } testCases := []testCase{ { name: "metadata error", clientMock: &mockedK8sClient{ metadata: func(context.Context) (string, string, error) { return "", "", fmt.Errorf("metadata error") }, }, expectedErr: fmt.Errorf("metadata error"), }, { name: "location error", clientMock: &mockedK8sClient{ location: func(context.Context) (*model.Location, error) { return nil, fmt.Errorf("location error") }, }, expectedErr: fmt.Errorf("location error"), }, { name: "all nodes error", clientMock: &mockedK8sClient{ allNodes: func(context.Context, bool) ([]model.Node, error) { return nil, fmt.Errorf("all nodes error") }, }, expectedErr: fmt.Errorf("all nodes error"), }, { name: "all resources error", clientMock: &mockedK8sClient{ allResources: func(context.Context, bool) (map[string]model.ResourceList, error) { return nil, fmt.Errorf("all resources error") }, }, expectedErr: fmt.Errorf("all resources error"), }, { name: "all images error", clientMock: &mockedK8sClient{ allImages: func(context.Context) ([]model.Image, error) { return nil, fmt.Errorf("all images error") }, }, expectedErr: fmt.Errorf("all images error"), }, { name: "print KBOM - stdout - wrong format", clientMock: &mockedK8sClient{}, timeMock: "2023-04-26T10:00:00.000000+00:00", idMock: "00000001", format: "wrong", expectedErr: fmt.Errorf("format \"wrong\" is not supported"), }, { name: "print KBOM - wrong output - JSON", clientMock: &mockedK8sClient{}, timeMock: "2023-04-26T10:00:00.000000+00:00", idMock: "00000001", output: "wrong", expectedErr: fmt.Errorf("output \"wrong\" is not supported"), }, { name: "print full KBOM - stdout - json", clientMock: &mockedK8sClient{ clusterName: func(context.Context) (string, error) { return "test-cluster", nil }, metadata: func(context.Context) (string, string, error) { return "012345678", "1.25.1", nil }, location: func(context.Context) (*model.Location, error) { return &model.Location{ Name: "aws", Region: "us-east-1", Zone: "us-east-1a", }, nil }, allNodes: func(context.Context, bool) ([]model.Node, error) { return []model.Node{ { Name: "ip-10-0-65-00.us-east-1.compute.internal", Type: "t3.small", Hostname: "ip-10-0-65-00.us-east-1.compute.internal", Capacity: &model.Capacity{ CPU: "2", Memory: "1970512Ki", Pods: "11", EphemeralStorage: "524275692Ki", }, Allocatable: &model.Capacity{ CPU: "1930m", Memory: "1483088Ki", Pods: "11", EphemeralStorage: "482098735124", }, Labels: map[string]string{ "beta.kubernetes.io/arch": "amd64", "beta.kubernetes.io/instance-type": "t3.small", "beta.kubernetes.io/os": "linux", "topology.kubernetes.io/region": "us-west-2", "topology.kubernetes.io/zone": "us-west-2a", }, Annotations: map[string]string{ "node.alpha.kubernetes.io/ttl": "0", }, MachineID: "00001", Architecture: "amd64", ContainerRuntimeVersion: "containerd://1.6.8+bottlerocket", BootID: "00001", KernelVersion: "5.15.59", KubeProxyVersion: "v1.24.6", KubeletVersion: "v1.24.6", OperatingSystem: "linux", OsImage: "Bottlerocket OS 1.11.1 (aws-k8s-1.24)", }, { Name: "ip-10-0-65-01.us-east-1.compute.internal", Type: "t3.small", Hostname: "ip-10-0-65-01.us-east-1.compute.internal", Capacity: &model.Capacity{ CPU: "2", Memory: "1970512Ki", Pods: "11", EphemeralStorage: "524275692Ki", }, Allocatable: &model.Capacity{ CPU: "1930m", Memory: "1483088Ki", Pods: "11", EphemeralStorage: "482098735124", }, Labels: map[string]string{ "beta.kubernetes.io/arch": "amd64", "beta.kubernetes.io/instance-type": "t3.small", "beta.kubernetes.io/os": "linux", "topology.kubernetes.io/region": "us-west-2", "topology.kubernetes.io/zone": "us-west-2a", }, Annotations: map[string]string{ "node.alpha.kubernetes.io/ttl": "0", }, MachineID: "00002", Architecture: "amd64", ContainerRuntimeVersion: "containerd://1.6.8+bottlerocket", BootID: "00002", KernelVersion: "5.15.59", KubeProxyVersion: "v1.24.6", KubeletVersion: "v1.24.6", OperatingSystem: "linux", OsImage: "Bottlerocket OS 1.11.1 (aws-k8s-1.24)", }, }, nil }, allImages: func(context.Context) ([]model.Image, error) { return []model.Image{ { Name: "nginx", Version: "1.17.1", FullName: "nginx:1.17.1", Digest: "sha256:0000000000000000000000000000000000000000000000000000000000000001", }, { Name: "redis", Version: "7.0.1", FullName: "redis:7.0.1", Digest: "sha256:0000000000000000000000000000000000000000000000000000000000000002", }, }, nil }, allResources: func(context.Context, bool) (map[string]model.ResourceList, error) { return map[string]model.ResourceList{ "/v1, Resource=namespaces": { Kind: "Namespace", APIVersion: "v1", Namespaced: false, ResourcesCount: 2, Resources: []model.Resource{ { Name: "backend", AdditionalProperties: map[string]string{"version": "v1.0.0"}, }, { Name: "frontend", AdditionalProperties: map[string]string{"version": "v2.0.0"}, }, }, }, }, nil }, }, timeMock: "2023-04-26T10:00:00.000000+00:00", idMock: "00000001", expectedErr: nil, expectedOut: expectedOutJSON, }, { name: "print KBOM - stdout - yaml", clientMock: &mockedK8sClient{}, timeMock: "2023-04-26T10:00:00.000000+00:00", idMock: "00000001", format: YAMLFormat.Name, expectedOut: expectedOutYAML, }, { name: "print KBOM - file - yaml", clientMock: &mockedK8sClient{}, timeMock: "2023-04-26T10:00:00.000000+00:00", idMock: "00000001", format: YAMLFormat.Name, output: FileOutput, expectedOut: expectedOutYAML, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { mock := &stdoutMock{buf: bytes.Buffer{}} out = mock kbomID = tc.idMock if tc.timeMock != "" { mockedTime, err := time.Parse(time.RFC3339, tc.timeMock) assert.NoError(t, err) generatedAt = mockedTime } if tc.format != "" { format = tc.format } else { format = JSONFormat.Name } if tc.output != "" { output = tc.output } else { output = StdOutput } err := generateKBOM(tc.clientMock) if tc.expectedErr != nil { assert.EqualError(t, err, tc.expectedErr.Error()) } else { assert.NoError(t, err) } if output == FileOutput { filename := fmt.Sprintf("kbom-%s-2023-04-26-10-00-00.%s", mockCACert[:8], format) assert.FileExists(t, filename) file, err := os.Open(filename) assert.NoError(t, err) buf := new(bytes.Buffer) _, err = buf.ReadFrom(file) assert.NoError(t, err) file.Close() assert.Equal(t, tc.expectedOut, buf.String()) assert.NoError(t, os.Remove(filename)) } else { assert.Equal(t, tc.expectedOut, mock.buf.String()) } }) } } type mockedK8sClient struct { clusterName func(context.Context) (string, error) metadata func(context.Context) (string, string, error) location func(context.Context) (*model.Location, error) allImages func(context.Context) ([]model.Image, error) allNodes func(context.Context, bool) ([]model.Node, error) allResources func(context.Context, bool) (map[string]model.ResourceList, error) } func (m *mockedK8sClient) ClusterName(ctx context.Context) (clusterName string, err error) { if m.clusterName == nil { return "test-cluster", nil } return m.clusterName(ctx) } func (m *mockedK8sClient) Metadata(ctx context.Context) (ver, ca string, err error) { if m.metadata == nil { return "1.25.1", mockCACert, nil } return m.metadata(ctx) } func (m *mockedK8sClient) Location(ctx context.Context) (*model.Location, error) { if m.location == nil { return nil, nil } return m.location(ctx) } func (m *mockedK8sClient) AllImages(ctx context.Context) ([]model.Image, error) { if m.allImages == nil { return nil, nil } return m.allImages(ctx) } func (m *mockedK8sClient) AllNodes(ctx context.Context, full bool) ([]model.Node, error) { if m.allNodes == nil { return nil, nil } return m.allNodes(ctx, full) } func (m *mockedK8sClient) AllResources(ctx context.Context, full bool) (map[string]model.ResourceList, error) { if m.allResources == nil { return nil, nil } return m.allResources(ctx, full) } var mockCACert = "1234567890" var expectedOutJSON = `{ "id": "00000001", "bom_format": "rad", "spec_version": "0.3", "generated_at": "2023-04-26T10:00:00Z", "generated_by": { "vendor": "RAD Security", "name": "unknown", "build_time": "unknown", "version": "unknown", "commit": "unknown", "commit_time": "unknown" }, "cluster": { "name": "test-cluster", "ca_cert_digest": "1.25.1", "k8s_version": "012345678", "location": { "name": "aws", "region": "us-east-1", "zone": "us-east-1a" }, "nodes_count": 2, "nodes": [ { "name": "ip-10-0-65-00.us-east-1.compute.internal", "type": "t3.small", "hostname": "ip-10-0-65-00.us-east-1.compute.internal", "capacity": { "cpu": "2", "memory": "1970512Ki", "pods": "11", "ephemeral_storage": "524275692Ki" }, "allocatable": { "cpu": "1930m", "memory": "1483088Ki", "pods": "11", "ephemeral_storage": "482098735124" }, "labels": { "beta.kubernetes.io/arch": "amd64", "beta.kubernetes.io/instance-type": "t3.small", "beta.kubernetes.io/os": "linux", "topology.kubernetes.io/region": "us-west-2", "topology.kubernetes.io/zone": "us-west-2a" }, "annotations": { "node.alpha.kubernetes.io/ttl": "0" }, "machine_id": "00001", "architecture": "amd64", "container_runtime_version": "containerd://1.6.8+bottlerocket", "boot_id": "00001", "kernel_version": "5.15.59", "kube_proxy_version": "v1.24.6", "kubelet_version": "v1.24.6", "operating_system": "linux", "os_image": "Bottlerocket OS 1.11.1 (aws-k8s-1.24)" }, { "name": "ip-10-0-65-01.us-east-1.compute.internal", "type": "t3.small", "hostname": "ip-10-0-65-01.us-east-1.compute.internal", "capacity": { "cpu": "2", "memory": "1970512Ki", "pods": "11", "ephemeral_storage": "524275692Ki" }, "allocatable": { "cpu": "1930m", "memory": "1483088Ki", "pods": "11", "ephemeral_storage": "482098735124" }, "labels": { "beta.kubernetes.io/arch": "amd64", "beta.kubernetes.io/instance-type": "t3.small", "beta.kubernetes.io/os": "linux", "topology.kubernetes.io/region": "us-west-2", "topology.kubernetes.io/zone": "us-west-2a" }, "annotations": { "node.alpha.kubernetes.io/ttl": "0" }, "machine_id": "00002", "architecture": "amd64", "container_runtime_version": "containerd://1.6.8+bottlerocket", "boot_id": "00002", "kernel_version": "5.15.59", "kube_proxy_version": "v1.24.6", "kubelet_version": "v1.24.6", "operating_system": "linux", "os_image": "Bottlerocket OS 1.11.1 (aws-k8s-1.24)" } ], "components": { "images": [ { "full_name": "nginx:1.17.1", "name": "nginx", "version": "1.17.1", "digest": "sha256:0000000000000000000000000000000000000000000000000000000000000001" }, { "full_name": "redis:7.0.1", "name": "redis", "version": "7.0.1", "digest": "sha256:0000000000000000000000000000000000000000000000000000000000000002" } ], "resources": { "/v1, Resource=namespaces": { "kind": "Namespace", "api_version": "v1", "namespaced": false, "count": 2, "resources": [ { "name": "backend", "additional_properties": { "version": "v1.0.0" } }, { "name": "frontend", "additional_properties": { "version": "v2.0.0" } } ] } } } } } ` var expectedOutYAML = `id: "00000001" bomformat: rad specversion: "0.3" generatedat: 2023-04-26T10:00:00Z generatedby: vendor: RAD Security name: unknown buildtime: unknown version: unknown commit: unknown committime: unknown cluster: name: test-cluster cacertdigest: "1234567890" k8sversion: 1.25.1 cniversion: "" location: null nodescount: 0 nodes: [] components: images: [] resources: {} ` 0707010000001C000081A4000000000000000000000001671FABC100000811000000000000000000000000000000000000001700000000kbom-0.3.1/cmd/root.gopackage cmd import ( "fmt" "io" "os" "path" "strings" "github.com/rs/zerolog" "github.com/rs/zerolog/log" "github.com/spf13/cobra" "github.com/spf13/viper" "golang.org/x/term" "github.com/rad-security/kbom/internal/utils" ) const ( confDir = ".config/rad" ) var ( verbose bool k8sContext string out io.WriteCloser = os.Stdout ) var rootCmd = &cobra.Command{ Use: "kbom", Short: "KBOM - Kubernetes Bill of Materials", PersistentPreRun: setup, } func Execute() { if err := rootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(1) } } func init() { rootCmd.AddCommand(GenerateCmd) rootCmd.AddCommand(versionCmd) rootCmd.AddCommand(schemaCmd) rootCmd.PersistentFlags().StringVarP(&k8sContext, "context", "c", "", "Kubernetes context to use, defaults to current context") rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Enable verbose logging (DEBUG and below)") rootCmd.SilenceErrors = true rootCmd.SilenceUsage = true } func setup(cmd *cobra.Command, _ []string) { initLogger() initConfig() utils.BindFlags(cmd) } func initConfig() { home, err := os.UserHomeDir() cobra.CheckErr(err) viper.AddConfigPath(home) viper.SetConfigType("json") viper.SetConfigName(path.Join(confDir, "kbom.json")) if err := viper.ReadInConfig(); err != nil { // It's okay if there isn't a config file if _, ok := err.(viper.ConfigFileNotFoundError); !ok { fmt.Println("error reading config file") os.Exit(1) } } // Environment variables can't have dashes viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) viper.AutomaticEnv() } func initLogger() { defaultLogger := zerolog.New(os.Stderr) logLevel := zerolog.InfoLevel if verbose { logLevel = zerolog.TraceLevel } zerolog.SetGlobalLevel(logLevel) // use color logger when run in terminal if isTerminal() { defaultLogger = zerolog.New(zerolog.NewConsoleWriter()) } log.Logger = defaultLogger.With().Timestamp().Stack().Logger() } func isTerminal() bool { return term.IsTerminal(int(os.Stdout.Fd())) //nolint:gosec } 0707010000001D000081A4000000000000000000000001671FABC100000163000000000000000000000000000000000000001C00000000kbom-0.3.1/cmd/root_test.gopackage cmd import ( "bytes" "testing" ) func TestRoot(t *testing.T) { var buf bytes.Buffer rootCmd.SetOut(&buf) Execute() // Check if output contains expected string expected := "KBOM - Kubernetes Bill of Materials" if !bytes.Contains(buf.Bytes(), []byte(expected)) { t.Errorf("Execute() output = %q, want %q", buf.String(), expected) } } 0707010000001E000081A4000000000000000000000001671FABC1000001D1000000000000000000000000000000000000001900000000kbom-0.3.1/cmd/schema.gopackage cmd import ( "encoding/json" "github.com/invopop/jsonschema" "github.com/spf13/cobra" "github.com/rad-security/kbom/internal/model" ) var schemaCmd = &cobra.Command{ Use: "schema", Short: "Print the KBOM json file schema", RunE: runGenerateSchema, } func runGenerateSchema(cmd *cobra.Command, _ []string) error { schema := jsonschema.Reflect(&model.KBOM{}) enc := json.NewEncoder(out) enc.SetIndent("", " ") return enc.Encode(schema) } 0707010000001F000081A4000000000000000000000001671FABC100001CEA000000000000000000000000000000000000001E00000000kbom-0.3.1/cmd/schema_test.gopackage cmd import ( "bytes" "testing" "github.com/stretchr/testify/assert" ) func TestRunGenerateSchema(t *testing.T) { mock := &stdoutMock{buf: bytes.Buffer{}} out = mock err := runGenerateSchema(nil, []string{}) assert.NoError(t, err) assert.Equal(t, expectedSchema, mock.buf.String()) } type stdoutMock struct { buf bytes.Buffer } func (m *stdoutMock) Write(p []byte) (n int, err error) { return m.buf.Write(p) } func (m *stdoutMock) Close() error { return nil } var expectedSchema = `{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/rad-security/kbom/internal/model/kbom", "$ref": "#/$defs/KBOM", "$defs": { "Capacity": { "properties": { "cpu": { "type": "string" }, "memory": { "type": "string" }, "pods": { "type": "string" }, "ephemeral_storage": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "cpu", "memory", "pods", "ephemeral_storage" ] }, "Cluster": { "properties": { "name": { "type": "string" }, "ca_cert_digest": { "type": "string" }, "k8s_version": { "type": "string" }, "cni_version": { "type": "string" }, "location": { "$ref": "#/$defs/Location" }, "nodes_count": { "type": "integer" }, "nodes": { "items": { "$ref": "#/$defs/Node" }, "type": "array" }, "components": { "$ref": "#/$defs/Components" } }, "additionalProperties": false, "type": "object", "required": [ "name", "ca_cert_digest", "k8s_version", "location", "nodes_count", "nodes", "components" ] }, "Components": { "properties": { "images": { "items": { "$ref": "#/$defs/Image" }, "type": "array" }, "resources": { "additionalProperties": { "$ref": "#/$defs/ResourceList" }, "type": "object" } }, "additionalProperties": false, "type": "object", "required": [ "resources" ] }, "Image": { "properties": { "full_name": { "type": "string" }, "name": { "type": "string" }, "version": { "type": "string" }, "digest": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "full_name", "name", "version", "digest" ] }, "KBOM": { "properties": { "id": { "type": "string" }, "bom_format": { "type": "string" }, "spec_version": { "type": "string" }, "generated_at": { "type": "string", "format": "date-time" }, "generated_by": { "$ref": "#/$defs/Tool" }, "cluster": { "$ref": "#/$defs/Cluster" } }, "additionalProperties": false, "type": "object", "required": [ "id", "bom_format", "spec_version", "generated_at", "generated_by", "cluster" ] }, "Location": { "properties": { "name": { "type": "string" }, "region": { "type": "string" }, "zone": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "name", "region", "zone" ] }, "Node": { "properties": { "name": { "type": "string" }, "type": { "type": "string" }, "hostname": { "type": "string" }, "capacity": { "$ref": "#/$defs/Capacity" }, "allocatable": { "$ref": "#/$defs/Capacity" }, "labels": { "additionalProperties": { "type": "string" }, "type": "object" }, "annotations": { "additionalProperties": { "type": "string" }, "type": "object" }, "machine_id": { "type": "string" }, "architecture": { "type": "string" }, "container_runtime_version": { "type": "string" }, "boot_id": { "type": "string" }, "kernel_version": { "type": "string" }, "kube_proxy_version": { "type": "string" }, "kubelet_version": { "type": "string" }, "operating_system": { "type": "string" }, "os_image": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "name", "type", "hostname", "capacity", "allocatable", "labels", "annotations", "machine_id", "architecture", "container_runtime_version", "boot_id", "kernel_version", "kube_proxy_version", "kubelet_version", "operating_system", "os_image" ] }, "Resource": { "properties": { "kind": { "type": "string" }, "api_version": { "type": "string" }, "name": { "type": "string" }, "namespace": { "type": "string" }, "additional_properties": { "additionalProperties": { "type": "string" }, "type": "object" } }, "additionalProperties": false, "type": "object", "required": [ "name" ] }, "ResourceList": { "properties": { "kind": { "type": "string" }, "api_version": { "type": "string" }, "namespaced": { "type": "boolean" }, "count": { "type": "integer" }, "resources": { "items": { "$ref": "#/$defs/Resource" }, "type": "array" } }, "additionalProperties": false, "type": "object", "required": [ "kind", "api_version", "namespaced", "count" ] }, "Tool": { "properties": { "vendor": { "type": "string" }, "name": { "type": "string" }, "build_time": { "type": "string" }, "version": { "type": "string" }, "commit": { "type": "string" }, "commit_time": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "vendor", "name", "build_time", "version", "commit", "commit_time" ] } } } ` 07070100000020000081A4000000000000000000000001671FABC10000025F000000000000000000000000000000000000001A00000000kbom-0.3.1/cmd/version.gopackage cmd import ( "fmt" "github.com/spf13/cobra" "github.com/rad-security/kbom/internal/config" ) var versionCmd = &cobra.Command{ Use: "version", Short: "Print the KBOM generator version", Long: `All software has versions. This is KBOM's`, RunE: runPrintVersion, } func runPrintVersion(cmd *cobra.Command, _ []string) error { fmt.Fprintf(out, "%s version %s\n", config.AppName, config.AppVersion) fmt.Fprintf(out, "build date: %s\n", config.BuildTime) fmt.Fprintf(out, "commit: %s\n\n", config.LastCommitHash) fmt.Fprintln(out, "https://github.com/rad-security/kbom") return nil } 07070100000021000081A4000000000000000000000001671FABC100000265000000000000000000000000000000000000001F00000000kbom-0.3.1/cmd/version_test.gopackage cmd import ( "bytes" "testing" "github.com/stretchr/testify/assert" "github.com/rad-security/kbom/internal/config" ) func TestVersion(t *testing.T) { mock := &stdoutMock{buf: bytes.Buffer{}} out = mock config.AppName = "kbom" config.AppVersion = "1.0.0" config.BuildTime = "2021-01-01T00:00:00Z" config.LastCommitHash = "1234567890" err := runPrintVersion(nil, []string{}) assert.NoError(t, err) assert.Equal(t, expectedVersion, mock.buf.String()) } var expectedVersion = `kbom version 1.0.0 build date: 2021-01-01T00:00:00Z commit: 1234567890 https://github.com/rad-security/kbom ` 07070100000022000041ED000000000000000000000002671FABC100000000000000000000000000000000000000000000001000000000kbom-0.3.1/docs07070100000023000081A4000000000000000000000001671FABC100000244000000000000000000000000000000000000001A00000000kbom-0.3.1/docs/schema.md# KBOM Schema The section below describes the high level object model for KBOM. ## Cluster Details Instances: - Name - Hostname - CloudType - Creation Timestamp - Capacity - Allocatable resources - OS Version - Kernel Version - Architecture - CRI Version - Kubelet Version - Kube Proxy Version Images: - Name - FullName - Version - Digest KubeObjects: - Kind - Api Version - Count - Details This overall structure provides a base spec to be expanded upon by the community. The intent of the standard is to be extensible to support various use cases across the industry. 07070100000024000081A4000000000000000000000001671FABC100000EFA000000000000000000000000000000000000001C00000000kbom-0.3.1/docs/taxonomy.md# Custom RAD KBOM Taxonomy This is the RAD KBOM CycloneDX property namespace and name taxonomy. All of the namespaces are prefixed with `rad:kbom:`. Following Taxonomy is used by the `KBOM` tool as extension to: [https://github.com/CycloneDX/cyclonedx-property-taxonomy](https://github.com/CycloneDX/cyclonedx-property-taxonomy). ## `rad:kbom:k8s:component` Namespace Taxonomy | Namespace | Description | | ------------------------------------ | ----------------------------------------------------------------- | | `rad:kbom:k8s:component:apiVersion` | API Version of the Kubernetes component. | | `rad:kbom:k8s:component:namespace` | Namespace of the Kubernetes component. | ## `rad:kbom:k8s:cluster` Namespace Taxonomy | Property | Description | | ----------------------------------------- | ------------------------------ | | `rad:kbom:k8s:cluster:location:name` | Name of the location. | | `rad:kbom:k8s:cluster:location:region` | Region of the cluster. | | `rad:kbom:k8s:cluster:location:zone` | Zone where cluster is located. | ## `rad:kbom:k8s:node` Namespace Taxonomy | Property | Description | | -------------------------------------------------- | ------------------------------------ | | `rad:kbom:k8s:node:osImage` | Node's operating system image | | `rad:kbom:k8s:node:arch` | Node's architecture | | `rad:kbom:k8s:node:kernel` | Node's kernel version | | `rad:kbom:k8s:node:bootId` | Node's Boot identifier | | `rad:kbom:k8s:node:type` | Node's type | | `rad:kbom:k8s:node:operatingSystem` | Node's operating system | | `rad:kbom:k8s:node:machineId` | Node's machine identifier | | `rad:kbom:k8s:node:hostname` | Node's hostname | | `rad:kbom:k8s:node:containerRuntimeVersion` | Node's container runtime version | | `rad:kbom:k8s:node:kubeletVersion` | Node's kubelet version | | `rad:kbom:k8s:node:kubeProxyVersion` | Node's kube proxy version | | `rad:kbom:k8s:node:capacity:cpu` | Node's CPU capacity | | `rad:kbom:k8s:node:capacity:memory` | Node's Memory capacity | | `rad:kbom:k8s:node:capacity:pods` | Node's Pods capacity | | `rad:kbom:k8s:node:capacity:ephemeralStorage` | Node's ephemeral storage capacity | | `rad:kbom:k8s:node:allocatable:cpu` | Node's allocatable CPU | | `rad:kbom:k8s:node:allocatable:memory` | Node's allocatable Memory | | `rad:kbom:k8s:node:allocatable:pods` | Node's allocatable Pods | | `rad:kbom:k8s:node:allocatable:ephemeralStorage` | Node's allocatable ephemeral storage | ## `rad:kbom:pkg` Namespace Taxonomy | Property | Description | | --------------------------------- | -------------------------------------------------- | | `rad:kbom:pkg:type` | Type of the package. | | `rad:kbom:pkg:name` | Name of the package. | | `rad:kbom:pkg:version` | Version of the package. | | `rad:kbom:pkg:digest` | Digest of the package. | 07070100000025000081A4000000000000000000000001671FABC100000D32000000000000000000000000000000000000001200000000kbom-0.3.1/go.modmodule github.com/rad-security/kbom go 1.23 require ( github.com/CycloneDX/cyclonedx-go v0.7.2 github.com/Masterminds/semver v1.5.0 github.com/distribution/reference v0.6.0 github.com/google/uuid v1.6.0 github.com/invopop/jsonschema v0.12.0 github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/rs/zerolog v1.33.0 github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.18.2 github.com/stretchr/testify v1.9.0 golang.org/x/term v0.20.0 gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.29.0 k8s.io/apimachinery v0.30.1 k8s.io/client-go v0.29.0 ) require ( github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/buger/jsonparser v1.1.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.22.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/imdario/mergo v0.3.6 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/net v0.23.0 // indirect golang.org/x/oauth2 v0.15.0 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/klog/v2 v2.120.1 // indirect k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) 07070100000026000081A4000000000000000000000001671FABC100005424000000000000000000000000000000000000001200000000kbom-0.3.1/go.sumgithub.com/CycloneDX/cyclonedx-go v0.7.2 h1:kKQ0t1dPOlugSIYVOMiMtFqeXI2wp/f5DBIdfux8gnQ= github.com/CycloneDX/cyclonedx-go v0.7.2/go.mod h1:K2bA+324+Og0X84fA8HhN2X066K7Bxz4rpMQ4ZhjtSk= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI= github.com/invopop/jsonschema v0.12.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= github.com/onsi/gomega v1.31.0 h1:54UJxxj6cPInHS3a35wm6BK/F9nHYueZ1NVujHDrnXE= github.com/onsi/gomega v1.31.0/go.mod h1:DW9aCi7U6Yi40wNVAvT6kzFnEVEI5n3DloYBiKiT6zk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/terminalstatic/go-xsd-validate v0.1.5 h1:RqpJnf6HGE2CB/lZB1A8BYguk8uRtcvYAPLCF15qguo= github.com/terminalstatic/go-xsd-validate v0.1.5/go.mod h1:18lsvYFofBflqCrvo1umpABZ99+GneNTw2kEEc8UPJw= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/api v0.29.0 h1:NiCdQMY1QOp1H8lfRyeEf8eOwV6+0xA6XEE44ohDX2A= k8s.io/api v0.29.0/go.mod h1:sdVmXoz2Bo/cb77Pxi71IPTSErEW32xa4aXwKH7gfBA= k8s.io/apimachinery v0.30.1 h1:ZQStsEfo4n65yAdlGTfP/uSHMQSoYzU/oeEbkmF7P2U= k8s.io/apimachinery v0.30.1/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= k8s.io/client-go v0.29.0 h1:KmlDtFcrdUzOYrBhXHgKw5ycWzc3ryPX5mQe0SkG3y8= k8s.io/client-go v0.29.0/go.mod h1:yLkXH4HKMAywcrD82KMSmfYg2DlE8mepPR4JGSo5n38= k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= 07070100000027000041ED000000000000000000000002671FABC100000000000000000000000000000000000000000000001400000000kbom-0.3.1/internal07070100000028000041ED000000000000000000000002671FABC100000000000000000000000000000000000000000000001B00000000kbom-0.3.1/internal/config07070100000029000081A4000000000000000000000001671FABC10000021F000000000000000000000000000000000000002500000000kbom-0.3.1/internal/config/config.gopackage config var ( // AppName - variable injected by -X flag during build AppName = "unknown" // AppVersion - variable injected by -X flag during build AppVersion = "unknown" // LastCommitTime - variable injected by -X flag during build LastCommitTime = "unknown" // LastCommitUser - variable injected by -X flag during build LastCommitUser = "unknown" // LastCommitHash - variable injected by -X flag during build LastCommitHash = "unknown" // BuildTime - variable injected by -X flag during build BuildTime = "unknown" ) 0707010000002A000041ED000000000000000000000002671FABC100000000000000000000000000000000000000000000001900000000kbom-0.3.1/internal/kube0707010000002B000081A4000000000000000000000001671FABC100002EC8000000000000000000000000000000000000002100000000kbom-0.3.1/internal/kube/kube.gopackage kube import ( "context" "crypto/sha256" "fmt" "os" "strings" "github.com/Masterminds/semver" "github.com/distribution/reference" "github.com/rs/zerolog/log" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" "github.com/rad-security/kbom/internal/model" ) type K8sClient interface { ClusterName(ctx context.Context) (string, error) Metadata(ctx context.Context) (string, string, error) Location(ctx context.Context) (*model.Location, error) AllImages(ctx context.Context) ([]model.Image, error) AllNodes(ctx context.Context, full bool) ([]model.Node, error) AllResources(ctx context.Context, full bool) (map[string]model.ResourceList, error) } func NewClient(k8sContext string) (K8sClient, error) { currentK8sContext := k8sContext cfg, err := rest.InClusterConfig() if err != nil { kubeConfigPath := os.Getenv("KUBECONFIG") if kubeConfigPath == "" { kubeConfigPath = os.Getenv("HOME") + "/.kube/config" } clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( &clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeConfigPath}, &clientcmd.ConfigOverrides{ CurrentContext: k8sContext, }) rawConfig, err := clientConfig.RawConfig() if err != nil { return nil, fmt.Errorf("failed to get kubernetes out-cluster client: %w", err) } if k8sContext == "" { currentK8sContext = rawConfig.CurrentContext } cfg, err = clientConfig.ClientConfig() if err != nil { return nil, fmt.Errorf("failed to get kubernetes out-cluster client: %w", err) } } clientset, err := kubernetes.NewForConfig(cfg) if err != nil { return nil, fmt.Errorf("can not create kubernetes client: %w", err) } dynamicClient, err := dynamic.NewForConfig(cfg) if err != nil { return nil, fmt.Errorf("can not create kubernetes dynamic client: %w", err) } rest.SetDefaultWarningHandler(rest.NoWarnings{}) return &k8sDB{ k8sContext: currentK8sContext, cfg: cfg, client: clientset, dynamicClient: dynamicClient, }, nil } type k8sDB struct { k8sContext string cfg *rest.Config client kubernetes.Interface dynamicClient dynamic.Interface } func (k *k8sDB) ClusterName(ctx context.Context) (string, error) { // TODO: find a better way to get cluster name, but for now use cluster context name return k.k8sContext, nil } func (k *k8sDB) Location(ctx context.Context) (*model.Location, error) { // fetch first node node, err := k.client.CoreV1().Nodes().List(ctx, metav1.ListOptions{Limit: 1}) if err != nil { return nil, fmt.Errorf("failed to list nodes: %v", err) } if len(node.Items) == 0 { return nil, fmt.Errorf("no node found") } // get location from node labels return &model.Location{ Name: getCloudName(node.Items[0].Labels), Region: getLabelValue(node.Items[0].Labels, "topology.kubernetes.io/region"), Zone: getLabelValue(node.Items[0].Labels, "topology.kubernetes.io/zone"), }, nil } // AllNodes returns all nodes in the cluster func (k *k8sDB) AllNodes(ctx context.Context, full bool) ([]model.Node, error) { nodes, err := k.client.CoreV1().Nodes().List(ctx, metav1.ListOptions{}) if err != nil { return nil, fmt.Errorf("failed to list nodes: %v", err) } modelNodes := make([]model.Node, 0) for i := range nodes.Items { var labels, annotations map[string]string if full { labels = nodes.Items[i].Labels annotations = nodes.Items[i].Annotations } modelNodes = append(modelNodes, model.Node{ Name: nodes.Items[i].Name, OsImage: nodes.Items[i].Status.NodeInfo.OSImage, Hostname: getLabelValue(nodes.Items[i].Labels, "kubernetes.io/hostname"), Type: getLabelValue(nodes.Items[i].Labels, "node.kubernetes.io/instance-type"), Capacity: &model.Capacity{ CPU: nodes.Items[i].Status.Capacity.Cpu().String(), Memory: nodes.Items[i].Status.Capacity.Memory().String(), EphemeralStorage: nodes.Items[i].Status.Capacity.StorageEphemeral().String(), Pods: nodes.Items[i].Status.Capacity.Pods().String(), }, Allocatable: &model.Capacity{ CPU: nodes.Items[i].Status.Allocatable.Cpu().String(), Memory: nodes.Items[i].Status.Allocatable.Memory().String(), EphemeralStorage: nodes.Items[i].Status.Allocatable.StorageEphemeral().String(), Pods: nodes.Items[i].Status.Allocatable.Pods().String(), }, Labels: labels, Annotations: annotations, MachineID: nodes.Items[i].Status.NodeInfo.MachineID, Architecture: nodes.Items[i].Status.NodeInfo.Architecture, KernelVersion: nodes.Items[i].Status.NodeInfo.KernelVersion, ContainerRuntimeVersion: nodes.Items[i].Status.NodeInfo.ContainerRuntimeVersion, BootID: nodes.Items[i].Status.NodeInfo.BootID, KubeProxyVersion: nodes.Items[i].Status.NodeInfo.KubeProxyVersion, KubeletVersion: nodes.Items[i].Status.NodeInfo.KubeletVersion, OperatingSystem: nodes.Items[i].Status.NodeInfo.OperatingSystem, }) } return modelNodes, nil } func (k *k8sDB) AllImages(ctx context.Context) ([]model.Image, error) { namespaces, err := k.client.CoreV1().Namespaces().List(ctx, metav1.ListOptions{}) if err != nil { return nil, fmt.Errorf("failed to list namespaces: %v", err) } images := make(map[string]model.Image) for i := range namespaces.Items { pods, err := k.client.CoreV1().Pods(namespaces.Items[i].Name).List(ctx, metav1.ListOptions{}) if err != nil { return nil, fmt.Errorf("failed to list pods: %w", err) } namespace := namespaces.Items[i].Name log.Debug().Str("namespace", namespace).Int("count", len(pods.Items)).Msg("Found pods in namespace") for j := range pods.Items { pod := pods.Items[j] for k := range pod.Spec.InitContainers { img, err := containerToImage(pod.Spec.InitContainers[k].Image, pod.Spec.InitContainers[k].Name, pod.Status.InitContainerStatuses, namespace) if err != nil { return nil, err } images[img.FullName] = *img } for k := range pod.Spec.Containers { img, err := containerToImage(pod.Spec.Containers[k].Image, pod.Spec.Containers[k].Name, pod.Status.ContainerStatuses, namespace) if err != nil { return nil, err } images[img.FullName] = *img } for k := range pod.Spec.EphemeralContainers { img, err := containerToImage(pod.Spec.EphemeralContainers[k].Image, pod.Spec.EphemeralContainers[k].Name, pod.Status.EphemeralContainerStatuses, namespace) if err != nil { return nil, err } images[img.FullName] = *img } } } toReturn := make([]model.Image, 0) for _, v := range images { toReturn = append(toReturn, v) } return toReturn, nil } func containerToImage(img, imgName string, statuses []v1.ContainerStatus, namespace string) (*model.Image, error) { if img == "" { return nil, fmt.Errorf("container %s has no image", img) } named, err := reference.ParseNormalizedNamed(img) if err != nil { return nil, err } controlPlane := false if namespace == "kube-system" { controlPlane = true } res := &model.Image{ FullName: img, ControlPlane: controlPlane, } res.Name = named.Name() tagged, ok := named.(reference.Tagged) if ok { res.Version = tagged.Tag() } digested, ok := named.(reference.Digested) if ok { res.Digest = digested.Digest().String() } // search in statuses for ImageID to get digest for i := range statuses { if imgName == statuses[i].Name { if statuses[i].State.Running == nil && statuses[i].State.Terminated == nil { break // We can get valid digest only from running or terminated containers } if strings.Contains(statuses[i].ImageID, "@") { res.Digest = strings.Split(statuses[i].ImageID, "@")[1] } else if strings.HasPrefix(statuses[i].ImageID, "sha256:") { res.Digest = statuses[i].ImageID } break } } return res, nil } // Metadata returns the kubernetes version func (k *k8sDB) Metadata(ctx context.Context) (k8sVersion, caDigest string, err error) { if _, err := rest.InClusterConfig(); err != nil { hash := sha256.Sum256(k.cfg.CAData) caDigest = fmt.Sprintf("%x", hash[:]) } else { caConfigMap, err := k.client.CoreV1().ConfigMaps("kube-system").Get(ctx, "kube-root-ca.crt", metav1.GetOptions{}) if err != nil { log.Debug().Err(err).Msg("failed to get kube-root-ca.crt") } else { caCert, ok := caConfigMap.Data["ca.crt"] if !ok { return "", "", fmt.Errorf("can't find 'ca.crt' in configMap 'kube-root-ca.crt'") } caDigest = fmt.Sprintf("%x", sha256.Sum256([]byte(caCert))) } } version, err := k.client.Discovery().ServerVersion() if err != nil { return caDigest, "", fmt.Errorf("error getting k8s version: %w", err) } ver := strings.Trim(version.GitVersion, "v") sVer, err := semver.NewVersion(ver) if err != nil { return caDigest, "", fmt.Errorf("error parsing k8s version: %w", err) } ver = fmt.Sprintf("%d.%d.%d", sVer.Major(), sVer.Minor(), sVer.Patch()) return ver, caDigest, nil } func (k *k8sDB) AllResources(ctx context.Context, full bool) (map[string]model.ResourceList, error) { apiResourceList, err := k.client.Discovery().ServerPreferredResources() if err != nil { return nil, fmt.Errorf("failed to get api groups: %w", err) } resourceMap := make(map[string]model.ResourceList) for _, apiResource := range apiResourceList { gv, err := schema.ParseGroupVersion(apiResource.GroupVersion) if err != nil { return nil, fmt.Errorf("failed to parse group version: %w", err) } for i := range apiResource.APIResources { res := apiResource.APIResources[i] gvr := schema.GroupVersionResource{ Group: gv.Group, Version: gv.Version, Resource: res.Name, } resourceList, err := k.dynamicClient.Resource(gvr).List(ctx, metav1.ListOptions{}) if err != nil { log.Debug().Err(err).Interface("gvr", gvr).Msg("Failed to list resources") continue } log.Debug().Interface("gvr", gvr).Int("count", len(resourceList.Items)).Msg("Found resources") if len(resourceList.Items) > 0 { resourceMap[gvr.String()] = model.ResourceList{ Kind: resourceList.Items[0].GetKind(), APIVersion: gvr.GroupVersion().String(), Namespaced: res.Namespaced, ResourcesCount: len(resourceList.Items), Resources: make([]model.Resource, 0), } } if full { for _, item := range resourceList.Items { res := model.Resource{ Name: item.GetName(), Namespace: item.GetNamespace(), AdditionalProperties: map[string]string{}, } if version, ok := getVersion(item); ok { res.AdditionalProperties["version"] = version } val := resourceMap[gvr.String()] val.Resources = append(val.Resources, res) resourceMap[gvr.String()] = val } } } } return resourceMap, nil } func getVersion(item unstructured.Unstructured) (version string, ok bool) { obj := item.Object if obj == nil { return "", false } spec, ok := obj["spec"].(map[string]interface{}) if !ok { return "", false } version, ok = spec["version"].(string) return } func getLabelValue(labels map[string]string, key string) string { for k, v := range labels { if k == key { return v } } return "" } func getCloudName(labels map[string]string) string { if labels == nil { return "unknown" } if _, ok := labels["k8s.io/cloud-provider-aws"]; ok { return "aws" } if _, ok := labels["topology.gke.io/zone"]; ok { return "gcloud" } if _, ok := labels["kubernetes.azure.com/cluster"]; ok { return "azure" } return "unknown" } 0707010000002C000041ED000000000000000000000002671FABC100000000000000000000000000000000000000000000001A00000000kbom-0.3.1/internal/model0707010000002D000081A4000000000000000000000001671FABC100000FEF000000000000000000000000000000000000002200000000kbom-0.3.1/internal/model/kbom.gopackage model import ( "fmt" "net/url" "strings" "time" ) const ( ociPrefix = "oci" k8sPrefix = "k8s" pkgPrefix = "pkg" kubernetesPkgName = "k8s.io/kubernetes" ) type KBOM struct { ID string `json:"id"` BOMFormat string `json:"bom_format"` SpecVersion string `json:"spec_version"` GeneratedAt time.Time `json:"generated_at"` GeneratedBy Tool `json:"generated_by"` Cluster Cluster `json:"cluster"` } type Tool struct { Vendor string `json:"vendor"` Name string `json:"name"` BuildTime string `json:"build_time"` Version string `json:"version"` Commit string `json:"commit"` CommitTime string `json:"commit_time"` } type Cluster struct { Name string `json:"name"` CACertDigest string `json:"ca_cert_digest"` K8sVersion string `json:"k8s_version"` CNIVersion string `json:"cni_version,omitempty"` Location *Location `json:"location"` NodesCount int `json:"nodes_count"` Nodes []Node `json:"nodes"` Components Components `json:"components"` } func (c *Cluster) BOMRef() string { return fmt.Sprintf("%s:%s/%s@%s", pkgPrefix, k8sPrefix, url.QueryEscape(kubernetesPkgName), c.K8sVersion) } func (c *Cluster) BOMName() string { return kubernetesPkgName } type Components struct { Images []Image `json:"images,omitempty"` Resources map[string]ResourceList `json:"resources"` } type Resource struct { Kind string `json:"kind,omitempty"` APIVersion string `json:"api_version,omitempty"` Name string `json:"name"` Namespace string `json:"namespace,omitempty"` AdditionalProperties map[string]string `json:"additional_properties,omitempty"` } type ResourceList struct { Kind string `json:"kind"` APIVersion string `json:"api_version"` Namespaced bool `json:"namespaced"` ResourcesCount int `json:"count"` Resources []Resource `json:"resources,omitempty"` } type Location struct { Name string `json:"name"` Region string `json:"region"` Zone string `json:"zone"` } type Node struct { Name string `json:"name"` Type string `json:"type"` Hostname string `json:"hostname"` Capacity *Capacity `json:"capacity"` Allocatable *Capacity `json:"allocatable"` Labels map[string]string `json:"labels"` Annotations map[string]string `json:"annotations"` MachineID string `json:"machine_id"` Architecture string `json:"architecture"` ContainerRuntimeVersion string `json:"container_runtime_version"` BootID string `json:"boot_id"` KernelVersion string `json:"kernel_version"` KubeProxyVersion string `json:"kube_proxy_version"` KubeletVersion string `json:"kubelet_version"` OperatingSystem string `json:"operating_system"` OsImage string `json:"os_image"` } type Image struct { FullName string `json:"full_name"` Name string `json:"name"` Version string `json:"version"` Digest string `json:"digest"` ControlPlane bool `json:"-"` } func (i *Image) PkgID() string { parts := strings.Split(i.Name, "/") baseName := fmt.Sprintf("%s:%s/%s", pkgPrefix, ociPrefix, parts[len(parts)-1]) urlValues := url.Values{ "repository_url": []string{i.Name}, } if i.Version != "" { urlValues.Add("tag", i.Version) } if i.Digest != "" { baseName = fmt.Sprintf("%s@%s", baseName, url.QueryEscape(i.Digest)) } return fmt.Sprintf("%s?%s", baseName, urlValues.Encode()) } type Capacity struct { CPU string `json:"cpu"` Memory string `json:"memory"` Pods string `json:"pods"` EphemeralStorage string `json:"ephemeral_storage"` } 0707010000002E000081A4000000000000000000000001671FABC1000004F3000000000000000000000000000000000000002700000000kbom-0.3.1/internal/model/kbom_test.gopackage model import ( "testing" ) func TestImagePkgID(t *testing.T) { testCases := []struct { name string image Image expectedID string expectedErr error }{ { name: "VersionAndDigest", image: Image{ FullName: "full_name", Name: "repo/name", Version: "version", Digest: "sha256:digest", }, expectedID: "pkg:oci/name@sha256%3Adigest?repository_url=repo%2Fname&tag=version", }, { name: "VersionOnly", image: Image{ FullName: "full_name", Name: "repo/name", Version: "version", }, expectedID: "pkg:oci/name?repository_url=repo%2Fname&tag=version", }, { name: "DigestOnly", image: Image{ FullName: "full_name", Name: "repo/subrepo/name", Digest: "sha256:digest", }, expectedID: "pkg:oci/name@sha256%3Adigest?repository_url=repo%2Fsubrepo%2Fname", }, { name: "NoVersionOrDigest", image: Image{ FullName: "full_name", Name: "repo/name", }, expectedID: "pkg:oci/name?repository_url=repo%2Fname", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { result := tc.image.PkgID() if result != tc.expectedID { t.Errorf("Expected %s, but got %s", tc.expectedID, result) } }) } } 0707010000002F000041ED000000000000000000000002671FABC100000000000000000000000000000000000000000000001A00000000kbom-0.3.1/internal/utils07070100000030000081A4000000000000000000000001671FABC1000001E5000000000000000000000000000000000000002300000000kbom-0.3.1/internal/utils/utils.gopackage utils import ( "fmt" "os" "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/spf13/viper" ) // BindFlags binds the viper config values to the flags func BindFlags(cmd *cobra.Command) { cmd.Flags().VisitAll(func(f *pflag.Flag) { configName := f.Name if !f.Changed && viper.IsSet(configName) { val := viper.Get(configName) err := cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val)) if err != nil { fmt.Println(err) os.Exit(1) } } }) } 07070100000031000081A4000000000000000000000001671FABC1000003A3000000000000000000000000000000000000002800000000kbom-0.3.1/internal/utils/utils_test.gopackage utils import ( "testing" "github.com/spf13/cobra" "github.com/spf13/viper" ) func TestBindFlags(t *testing.T) { // Set up a new cobra command cmd := &cobra.Command{ Use: "test", Run: func(cmd *cobra.Command, args []string) {}, } // Add some flags to the command cmd.Flags().String("foo", "", "foo flag") cmd.Flags().Int("bar", 0, "bar flag") // Initialize viper with some values viper.Set("foo", "foo-value") viper.Set("bar", 123) // Bind the viper config values to the command flags BindFlags(cmd) // Check that the flag values were set correctly fooFlag := cmd.Flags().Lookup("foo") if fooFlag.Value.String() != "foo-value" { t.Errorf("expected foo flag value to be 'foo-value', but got '%s'", fooFlag.Value.String()) } barFlag := cmd.Flags().Lookup("bar") if barFlag.Value.String() != "123" { t.Errorf("expected bar flag value to be '123', but got '%s'", barFlag.Value.String()) } } 07070100000032000081A4000000000000000000000001671FABC100000058000000000000000000000000000000000000001300000000kbom-0.3.1/main.gopackage main import "github.com/rad-security/kbom/cmd" func main() { cmd.Execute() } 07070100000033000081A4000000000000000000000001671FABC10000006B000000000000000000000000000000000000001900000000kbom-0.3.1/renovate.json{ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:base" ] } 07070100000034000041ED000000000000000000000002671FABC100000000000000000000000000000000000000000000001300000000kbom-0.3.1/scripts07070100000035000081ED000000000000000000000001671FABC10000032D000000000000000000000000000000000000001B00000000kbom-0.3.1/scripts/version#!/bin/bash set -euo pipefail SEP=${SEP:-"-"} exact_tag=$(git describe --exact-match 2>/dev/null | sed -e 's/^v//g' || echo '') last_tag=$(git describe --tags --abbrev=0 2>/dev/null) commits=$(git log --oneline "${last_tag}"..HEAD | wc -l | tr -d ' ') revision=$(git rev-parse --short HEAD || echo unknown) if echo "${exact_tag}" | grep -qE "^[0-9]+\.[0-9]+\.[0-9]+$"; then echo "$exact_tag" exit 0 fi if echo "${exact_tag}" | grep -qE "^[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$"; then echo "$exact_tag" exit 0 fi if echo "${exact_tag}" | grep -qE "^[0-9]+\.[0-9]+\.[0-9]+-rc[0-9]+$"; then echo "$exact_tag" exit 0 fi if [ "$commits" -eq 0 ]; then if [ -n "$last_tag" ]; then echo "${last_tag//v/}" exit 0 fi fi echo "${last_tag//v/}${SEP}${commits}-${revision}" 07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!248 blocks
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor