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


[](https://goreportcard.com/report/github.com/rad-security/kbom)
[](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