[TC-3272] Convert gensbom to TypeScript, add several security checks#2334
[TC-3272] Convert gensbom to TypeScript, add several security checks#2334i386x wants to merge 8 commits intoguacsec:mainfrom
gensbom to TypeScript, add several security checks#2334Conversation
Related: TC-3272 Assisted-by: Claude Code Signed-off-by: Jiří Kučera <jkucera@redhat.com>
Fix `tsconfig.json` and `package.json` to reflect modern NodeJS (for NodeJS >= 20) project settings and layout. Add settings for linters. Signed-off-by: Jiří Kučera <jkucera@redhat.com>
Migrate to `src/` and `dist/` project layout. Split project to more files. Add requested security checks (TC-3272). Do not fail when `TPA_SERVICE_URL` is not set, just generate SBOMs with Syft and do not ingest them to TPA. Signed-off-by: Jiří Kučera <jkucera@redhat.com>
Signed-off-by: Jiří Kučera <jkucera@redhat.com>
Signed-off-by: Jiří Kučera <jkucera@redhat.com>
Signed-off-by: Jiří Kučera <jkucera@redhat.com>
Signed-off-by: Jiří Kučera <jkucera@redhat.com>
Also fix replacing all the characters. Signed-off-by: Jiří Kučera <jkucera@redhat.com>
Reviewer's GuideThis PR replaces the previous Bash-based Sequence diagram for gensbom CLI execution and security checkssequenceDiagram
actor User
participant CLI as gensbom_bin
participant Main as main
participant Config as Config
participant Gen as SbomGenerator
participant TPA as TPAService
participant Syft as syft_binary
participant TPAHTTP as TPA_HTTP_API
User->>CLI: invoke gensbom images.txt
CLI->>Main: main()
Main->>Config: new Config(env)
Main->>Config: validate()
Config->>Config: checkTpaServiceUrl()
Config->>Config: validateDockerConfig()
Config-->>Config: parse config.json
Config-->>Config: validate quay.io robot credentials
Config->>Config: checkTpaAuthToken()
Config->>Config: checkTrustCert()
Main->>Gen: new SbomGenerator(config)
Gen->>TPA: new TPAService(config)
Main->>Gen: generate("images.txt")
Gen->>TPA: ping()
TPA->>TPAHTTP: GET /api/v2/sbom?limit=1
TPAHTTP-->>TPA: response or error
loop for each image in file
Gen->>Syft: exec syft scan image -o cyclonedx-json
Syft-->>Gen: SBOM file path or failure
Gen->>TPA: ingest(sbomPath)
alt SBOM valid and TPA configured
TPA->>TPAHTTP: POST /api/v2/sbom
TPAHTTP-->>TPA: 2xx/4xx/5xx
else
TPA-->>Gen: log warning
end
end
Gen->>Gen: pack() create sboms.zip
Gen-->>Main: resolve
Main-->>CLI: return exit code
Class diagram for the new TypeScript gensbom coreclassDiagram
direction LR
class FatalError {
+number errorCode
+constructor(message: string, errorCode: number)
}
class Config {
+string shell
+string tpaServiceUrl
+string dockerConfig
+string tpaAuthToken
+string trustCert
+constructor(environ: ProcessEnv)
+validate() void
+checkTpaServiceUrl() void
+validateDockerConfig() void
+checkTpaAuthToken() void
+checkTrustCert() void
}
class ConfigError {
+constructor(cfgfile: string, message: string)
}
class InsecureConfigError {
+constructor(cfgfile: string, message: string)
}
class UnsupportedOCIRegistryError {
+constructor(cfgfile: string, registry: string)
}
class TPAService {
+string baseUrl
+AxiosInstance service
+constructor(config: Config)
+ping() Promise~void~
+ingest(sbom: string) Promise~void~
}
class SbomGenerator {
-string shell
-TPAService tpaService
-boolean prepared
-number imageCount
-string sbomsDir
-string sbomsArchive
+constructor(config: Config)
+prepare() void
+sbomFileFromImageURI(image: string) string
+runSyft(image: string) string
+pack() Promise~void~
+generate(inputFile: string) Promise~void~
}
class HelpFormatter {
-Formatter[] lines
+constructor()
+blankLine() void
+usage(content: string) HelpFormatter
+par(content: string) HelpFormatter
+codeblock(content: CodeBlockItem) HelpFormatter
+warning(content: string) HelpFormatter
+section(content: string) HelpFormatter
+term(name: string, description: string) HelpFormatter
+format(style: Style) string
}
class Chunk {
#string content
+constructor(content: string)
}
class Comment {
+format(style: Style) string
}
class Code {
+format(style: Style) string
}
class Usage {
+constructor(content: string)
}
class Term {
+format(style: Style) string
}
class QuotedTerm {
+format(style: Style) string
}
class Warning {
+constructor()
+format(style: Style) string
}
class Section {
+format(style: Style) string
}
class Line {
-Fragment[] fragments
+constructor(fragments: Fragment)
+format(style: Style) string
}
class Utils {
<<utility>>
+scriptName() string
+stringify(obj: object, indent: string) string
+inform(messages: string) void
+warning(messages: string) void
+werror(message: string, color: Color) void
+handleError(err: unknown) number
+workspace() string
+isFile(path: string) boolean
+isNonEmptyFile(path: string) boolean
}
class MainModule {
<<module>>
+main() Promise~number~
}
FatalError <|-- InsecureConfigError
InsecureConfigError <|-- UnsupportedOCIRegistryError
Error <|-- FatalError
Error <|-- ConfigError
Config ..> Utils : uses
Config ..> FatalError : throws
TPAService ..> Config : reads
TPAService ..> Utils : logging
SbomGenerator ..> Config : uses
SbomGenerator ..> TPAService : aggregates
SbomGenerator ..> Utils : fileHelpers
HelpFormatter o-- Line
Line o-- Chunk
Chunk <|-- Comment
Chunk <|-- Code
Code <|-- Usage
Chunk <|-- Term
Term <|-- QuotedTerm
Chunk <|-- Warning
Chunk <|-- Section
MainModule ..> Config
MainModule ..> SbomGenerator
MainModule ..> Utils : handleError
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
So if we're converting this away from Bash, which is fine. Why don't we use the programming language we already use? Why duplicate a lot of stuff in a different language? I'd prefer this to be written in Rust. Pro side, no need to install any node interpreter or dependencies. Just a binary. And we already have a lot of CLI infrastructure. |
✔️
Sounds good then this can be more easy to transform to Rust now 👍
👍
There are some other hidden issues in this section... one of them is taking care of npm dependencies (dependabot sending PRs, security fixes, future linting problems with updates, maybe other folks have ruby or python or java background before Rust, and etc...) plus: Multi-"runtime"/platform maintenance adds extra work |
Rust was also on my list of options, and now when I see how much pain the conversion to Type Script costs me I must give you 👍 . The reason why I choose Type Script over Rust was mainly to reuse some bits from front end's e2e tests. However having this feature as a sub-command to Trustify CLI seems to be the best option to me. |
|
So my suggestion would then be, let's close this one and to it in Rust. |
gensbomwas converted to TypeScript from Bash using Claude Code as the first step, then it was manually refactored and split into several files to reflect a modern TypeScript project structure. Security checks requested by TC-3272 were also added.Summary by Sourcery
Convert the gensbom SBOM generation tool from a Bash script into a TypeScript-based Node.js CLI and container image, adding stricter configuration validation and TPA integration while introducing a modern build, lint, and formatting setup.
New Features:
Enhancements:
Build: