Skip to content

Commit

Permalink
Introduce "cyclonedx rename-entity" command (#346)
Browse files Browse the repository at this point in the history
Primarily written as a practical test case for `Bom.WalkThis()` and
`Bom.RenameBomRef()` methods introduced in the library, but may be
useful to have exposed for end-users.

Relies on CycloneDX/cyclonedx-dotnet-library#245
for the bulk of work (BomEntity base-class and interface family, etc.)
and CycloneDX/cyclonedx-dotnet-library#256 for
metadata update of the output document.

---------

Signed-off-by: Jim Klimov <jimklimov@gmail.com>
  • Loading branch information
jimklimov authored Aug 11, 2024
1 parent 86f7a19 commit f43675e
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 0 deletions.
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,55 @@ Merge two XML formatted BOMs:
Merging two BOMs and piping output to additional tools:
`cyclonedx-cli merge --input-files sbom1.xml sbom2.xml --output-format json | grep "something"`

## Rename Entity command

Rename an entity identified by "bom-ref" (formally a "refType") in the document
and/or back-references to such entity (formally a "refLinkType", typically as
a "ref" property; or certain lists' items).

```
rename-entity
Rename an entity identified by a "bom-ref" (including back-references to it) in the BOM document
Usage:
cyclonedx [options] rename-entity
Options:
--input-file <input-file> Input BOM filename.
--output-file <output-file> Output BOM filename, will write to stdout if no value provided.
--old-ref <old-ref> Old value of "bom-ref" entity identifier (or "ref" values or certain list items pointing to it).
--new-ref <new-ref> New value of "bom-ref" entity identifier (or "ref" values or certain list items pointing to it).
--input-format <autodetect|json|protobuf|xml> Specify input file format.
--output-format <autodetect|json|protobuf|xml> Specify output file format.
```

Keep in mind that these identifiers are arbitrary strings that have a meaning
within the Bom document (and should uniquely identify one entity in its scope).
While in some cases these identifiers are meaningful (e.g. "purl" values used
as "bom-ref" by the cyclonedx-maven-plugin), they may also validly be random
UUIDs or collision-prone strings like "1", "2", "3"...

They may be opportunistically used as anchors for cross-document references,
so in some cases a back-reference may point to a string for which there is no
"bom-ref" in the same document (see relevant CycloneDX specification version
for details).

This renaming operation also modifies the output document metadata, to reflect
the modification compared to the input document.

Basic error-checking, such as attempt to re-use an already existing identifier,
is performed.

### Examples

Rename an entity:
```
cyclonedx rename-entity --input-file sbom.json --output-format xml \
--oldref "pkg:maven/org.yaml/snakeyaml@1.33?type=jar" \
--newref "thirdpartylibs:org.yaml:snakeyaml:1.33:jar" \
| grep "thirdparty"
```

## Sign Command

Sign a BOM or file
Expand Down
101 changes: 101 additions & 0 deletions src/cyclonedx/Commands/RenameEntityCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// This file is part of CycloneDX CLI Tool
//
// 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.
//
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) OWASP Foundation. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.Threading.Tasks;
using CycloneDX.Models;
using CycloneDX.Utils;
using System.IO;
using System.Collections.Immutable;

namespace CycloneDX.Cli.Commands
{
public static class RenameEntityCommand
{
public static void Configure(RootCommand rootCommand)
{
Contract.Requires(rootCommand != null);
var subCommand = new System.CommandLine.Command("rename-entity", "Rename an entity identified by a \"bom-ref\" (including back-references to it) in the BOM document");
subCommand.Add(new Option<string>("--input-file", "Input BOM filename."));
subCommand.Add(new Option<string>("--output-file", "Output BOM filename, will write to stdout if no value provided."));
subCommand.Add(new Option<string>("--old-ref", "Old value of \"bom-ref\" entity identifier (or \"ref\" values or certain list items pointing to it)."));
subCommand.Add(new Option<string>("--new-ref", "New value of \"bom-ref\" entity identifier (or \"ref\" values or certain list items pointing to it)."));
subCommand.Add(new Option<CycloneDXBomFormat>("--input-format", "Specify input file format."));
subCommand.Add(new Option<CycloneDXBomFormat>("--output-format", "Specify output file format."));
subCommand.Handler = CommandHandler.Create<RenameEntityCommandOptions>(RenameEntity);
rootCommand.Add(subCommand);
}

public static async Task<int> RenameEntity(RenameEntityCommandOptions options)
{
Contract.Requires(options != null);
var outputToConsole = string.IsNullOrEmpty(options.OutputFile);

if (options.OutputFormat == CycloneDXBomFormat.autodetect)
{
options.OutputFormat = CliUtils.AutoDetectBomFormat(options.OutputFile);
if (options.OutputFormat == CycloneDXBomFormat.autodetect)
{
Console.WriteLine($"Unable to auto-detect output format");
return (int)ExitCode.ParameterValidationError;
}
}

Console.WriteLine($"Loading input document...");
if (!outputToConsole) Console.WriteLine($"Processing input file {options.InputFile}");
var bom = await CliUtils.InputBomHelper(options.InputFile, options.InputFormat).ConfigureAwait(false);

if (bom is null)
{
Console.WriteLine($"Empty or absent input document");
return (int)ExitCode.ParameterValidationError;
}

Console.WriteLine($"Beginning Bom walk to discover all identifiers (this can take a while)");
BomWalkResult bwr = bom.WalkThis();

Console.WriteLine($"Beginning Bom walk rename processing (this can take a while)");
if (bom.RenameBomRef(options.OldRef, options.NewRef, bwr))
{
Console.WriteLine($"Did not encounter any issues during the rename operation");
}
else
{
Console.WriteLine($"Rename operation failed non-fatally (e.g. old ref name not mentioned in the Bom document)");
}

// Ensure that the modified document has its own identity
// (new SerialNumber, Version=1, Timestamp...) and its Tools
// collection refers to this library and the program/tool
// like cyclonedx-cli which consumes it:
bom.BomMetadataUpdate(true);
bom.BomMetadataReferThisToolkit();

if (!outputToConsole)
{
Console.WriteLine("Writing output file...");
Console.WriteLine($" Total {bom.Components?.Count ?? 0} components, {bom.Dependencies?.Count ?? 0} dependencies");
}

int res = await CliUtils.OutputBomHelper(bom, options.OutputFormat, options.OutputFile).ConfigureAwait(false);
return res;
}
}
}
30 changes: 30 additions & 0 deletions src/cyclonedx/Commands/RenameEntityCommandOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// This file is part of CycloneDX CLI Tool
//
// 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.
//
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) OWASP Foundation. All Rights Reserved.
using System.Collections.Generic;

namespace CycloneDX.Cli.Commands
{
public class RenameEntityCommandOptions
{
public string InputFile { get; set; }
public string OutputFile { get; set; }
public string OldRef { get; set; }
public string NewRef { get; set; }
public CycloneDXBomFormat InputFormat { get; set; }
public CycloneDXBomFormat OutputFormat { get; set; }
}
}
1 change: 1 addition & 0 deletions src/cyclonedx/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public static async Task<int> Main(string[] args)
DiffCommand.Configure(rootCommand);
KeyGenCommand.Configure(rootCommand);
MergeCommand.Configure(rootCommand);
RenameEntityCommand.Configure(rootCommand);
SignCommand.Configure(rootCommand);
ValidateCommand.Configure(rootCommand);
VerifyCommand.Configure(rootCommand);
Expand Down

0 comments on commit f43675e

Please sign in to comment.