Skip to content

Commit fb49cf2

Browse files
authored
Merge pull request #6221 from nscuro/issue-6217
v4-migrator: Populate PERMISSION table in bootstrap phase
2 parents 21326bf + 3562f9d commit fb49cf2

7 files changed

Lines changed: 223 additions & 74 deletions

File tree

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* This file is part of Dependency-Track.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
* Copyright (c) OWASP Foundation. All Rights Reserved.
18+
*/
19+
package org.dependencytrack.v4migrator;
20+
21+
import org.jdbi.v3.core.Jdbi;
22+
23+
/**
24+
* Seeds the v5 {@code PERMISSION} catalog from the apiserver's
25+
* {@code org.dependencytrack.auth.Permissions} enum at Flyway head
26+
* {@code 202605111028}. Keep this in sync with the enum.
27+
*
28+
* <p>The migrator owns v5 PERMISSION seeding because downstream join-table loads
29+
* ({@code USERS_PERMISSIONS}, {@code TEAMS_PERMISSIONS}) need to FK-resolve v5
30+
* permission IDs (including v5-only entries such as
31+
* {@code PORTFOLIO_ACCESS_CONTROL_BYPASS}) before the apiserver runs its own
32+
* seeding step on first post-migration boot.
33+
*
34+
* <p>Seeding runs in {@code bootstrap} rather than {@code transform} so that a
35+
* documented "drop v5 schema, re-bootstrap, re-run load" recovery (see ADR-023
36+
* §Resumability and the user-facing migration guide) leaves PERMISSION populated
37+
* without re-running {@code transform}. The INSERT uses
38+
* {@code ON CONFLICT ("NAME") DO NOTHING} and is safe to invoke repeatedly.
39+
*/
40+
public final class PermissionCatalog {
41+
42+
private static final String SEED_SQL = """
43+
INSERT INTO "PERMISSION" ("NAME", "DESCRIPTION") VALUES
44+
('BOM_UPLOAD', 'Allows the ability to upload CycloneDX Software Bill of Materials (SBOM)'),
45+
('VIEW_PORTFOLIO', 'Provides the ability to view the portfolio of projects, components, and licenses'),
46+
('PORTFOLIO_ACCESS_CONTROL_BYPASS', 'Provides the ability to bypass portfolio access control, granting access to all projects'),
47+
('PORTFOLIO_MANAGEMENT', 'Allows the creation, modification, and deletion of data in the portfolio'),
48+
('PORTFOLIO_MANAGEMENT_CREATE', 'Allows the creation of data in the portfolio'),
49+
('PORTFOLIO_MANAGEMENT_READ', 'Allows the reading of data in the portfolio'),
50+
('PORTFOLIO_MANAGEMENT_UPDATE', 'Allows the updating of data in the portfolio'),
51+
('PORTFOLIO_MANAGEMENT_DELETE', 'Allows the deletion of data in the portfolio'),
52+
('VIEW_VULNERABILITY', 'Provides the ability to view the vulnerabilities projects are affected by'),
53+
('VULNERABILITY_ANALYSIS', 'Provides all abilities to make analysis decisions on vulnerabilities'),
54+
('VULNERABILITY_ANALYSIS_CREATE', 'Provides the ability to upload supported VEX documents to a project'),
55+
('VULNERABILITY_ANALYSIS_READ', 'Provides the ability read the VEX document for a project'),
56+
('VULNERABILITY_ANALYSIS_UPDATE', 'Provides the ability to make analysis decisions on vulnerabilities and upload supported VEX documents for a project'),
57+
('VIEW_POLICY_VIOLATION', 'Provides the ability to view policy violations'),
58+
('VULNERABILITY_MANAGEMENT', 'Allows all management permissions of internally-defined vulnerabilities'),
59+
('VULNERABILITY_MANAGEMENT_CREATE', 'Allows creation of internally-defined vulnerabilities'),
60+
('VULNERABILITY_MANAGEMENT_READ', 'Allows reading internally-defined vulnerabilities'),
61+
('VULNERABILITY_MANAGEMENT_UPDATE', 'Allows updating internally-defined vulnerabilities and vulnerability tags'),
62+
('VULNERABILITY_MANAGEMENT_DELETE', 'Allows management of internally-defined vulnerabilities'),
63+
('POLICY_VIOLATION_ANALYSIS', 'Provides the ability to make analysis decisions on policy violations'),
64+
('ACCESS_MANAGEMENT', 'Allows the management of users, teams, and API keys'),
65+
('ACCESS_MANAGEMENT_CREATE', 'Allows create permissions of users, teams, and API keys'),
66+
('ACCESS_MANAGEMENT_READ', 'Allows read permissions of users, teams, and API keys'),
67+
('ACCESS_MANAGEMENT_UPDATE', 'Allows update permissions of users, teams, and API keys'),
68+
('ACCESS_MANAGEMENT_DELETE', 'Allows delete permissions of users, teams, and API keys'),
69+
('SECRET_MANAGEMENT', 'Grants full secret management access'),
70+
('SECRET_MANAGEMENT_CREATE', 'Grants the ability to create secrets'),
71+
('SECRET_MANAGEMENT_UPDATE', 'Grants the ability to update secrets'),
72+
('SECRET_MANAGEMENT_DELETE', 'Grants the ability to delete secrets'),
73+
('SYSTEM_CONFIGURATION', 'Allows all access to configuration of the system including notifications, repositories, and email settings'),
74+
('SYSTEM_CONFIGURATION_CREATE', 'Allows creating configuration of the system including notifications, repositories, and email settings'),
75+
('SYSTEM_CONFIGURATION_READ', 'Allows reading the configuration of the system including notifications, repositories, and email settings'),
76+
('SYSTEM_CONFIGURATION_UPDATE', 'Allows updating the configuration of the system including notifications, repositories, and email settings'),
77+
('SYSTEM_CONFIGURATION_DELETE', 'Allows deleting the configuration of the system including notifications, repositories, and email settings'),
78+
('PROJECT_CREATION_UPLOAD', 'Provides the ability to optionally create project (if non-existent) on BOM or scan upload'),
79+
('POLICY_MANAGEMENT', 'Allows the creation, modification, and deletion of policy'),
80+
('POLICY_MANAGEMENT_CREATE', 'Allows the creation of a policy'),
81+
('POLICY_MANAGEMENT_READ', 'Allows reading of policies'),
82+
('POLICY_MANAGEMENT_UPDATE', 'Allows the modification of a policy'),
83+
('POLICY_MANAGEMENT_DELETE', 'Allows the deletion of a policy'),
84+
('TAG_MANAGEMENT', 'Allows the modification and deletion of tags'),
85+
('TAG_MANAGEMENT_DELETE', 'Allows the deletion of a tag')
86+
ON CONFLICT ("NAME") DO NOTHING
87+
""";
88+
89+
private PermissionCatalog() {
90+
}
91+
92+
public static int seed(final Jdbi target) {
93+
return target.withHandle(h -> h.execute(SEED_SQL));
94+
}
95+
}

support/v4-migrator/src/main/java/org/dependencytrack/v4migrator/TableRegistry.java

Lines changed: 8 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -595,14 +595,14 @@ ON CONFLICT ("USERNAME") DO NOTHING
595595
);
596596

597597
/**
598-
* Source-only mirror of v4 {@code PERMISSION}. The migrator seeds v5 {@code PERMISSION}
599-
* with the full v5 catalog (ON CONFLICT DO NOTHING) and builds
600-
* {@code permission_name_map} by inner-joining v4 NAME against v5 PERMISSION.
601-
* v4 permission names that no longer exist in v5 (e.g. {@code VIEW_BADGES}) drop out
602-
* of the map. Implication fan-out (v4 {@code ACCESS_MANAGEMENT} ->
603-
* v5 {@code PORTFOLIO_ACCESS_CONTROL_BYPASS}) is applied on the join-table
604-
* {@code tgt_*} tables. See {@code TEAMS_PERMISSIONS} and the consolidated
605-
* {@code USERS_PERMISSIONS} transforms.
598+
* Source-only mirror of v4 {@code PERMISSION}. The v5 {@code PERMISSION} catalog is
599+
* seeded during {@code bootstrap} (see {@link PermissionCatalog}); transform here
600+
* just builds {@code permission_name_map} by inner-joining v4 NAME against the
601+
* already-seeded v5 PERMISSION table. v4 permission names that no longer exist in
602+
* v5 (e.g. {@code VIEW_BADGES}) drop out of the map. Implication fan-out (v4
603+
* {@code ACCESS_MANAGEMENT} -> v5 {@code PORTFOLIO_ACCESS_CONTROL_BYPASS}) is
604+
* applied on the join-table {@code tgt_*} tables. See {@code TEAMS_PERMISSIONS} and
605+
* the consolidated {@code USERS_PERMISSIONS} transforms.
606606
*/
607607
private static final TableMigration PERMISSION = new TableMigration(
608608
"PERMISSION",
@@ -620,56 +620,6 @@ ON CONFLICT ("USERNAME") DO NOTHING
620620
""",
621621
List.of("ID", "DESCRIPTION", "NAME"),
622622
"""
623-
-- Seed v5 PERMISSION with the full catalog from apiserver's
624-
-- org.dependencytrack.auth.Permissions enum at Flyway head 202605111028.
625-
-- Keep this in sync with the enum; the migrator owns v5 PERMISSION seeding so
626-
-- that join-table loads further down can resolve v5-only permission IDs (e.g.
627-
-- PORTFOLIO_ACCESS_CONTROL_BYPASS) before the apiserver runs its own seeding
628-
-- step on first post-migration boot.
629-
INSERT INTO "PERMISSION" ("NAME", "DESCRIPTION") VALUES
630-
('BOM_UPLOAD', 'Allows the ability to upload CycloneDX Software Bill of Materials (SBOM)'),
631-
('VIEW_PORTFOLIO', 'Provides the ability to view the portfolio of projects, components, and licenses'),
632-
('PORTFOLIO_ACCESS_CONTROL_BYPASS', 'Provides the ability to bypass portfolio access control, granting access to all projects'),
633-
('PORTFOLIO_MANAGEMENT', 'Allows the creation, modification, and deletion of data in the portfolio'),
634-
('PORTFOLIO_MANAGEMENT_CREATE', 'Allows the creation of data in the portfolio'),
635-
('PORTFOLIO_MANAGEMENT_READ', 'Allows the reading of data in the portfolio'),
636-
('PORTFOLIO_MANAGEMENT_UPDATE', 'Allows the updating of data in the portfolio'),
637-
('PORTFOLIO_MANAGEMENT_DELETE', 'Allows the deletion of data in the portfolio'),
638-
('VIEW_VULNERABILITY', 'Provides the ability to view the vulnerabilities projects are affected by'),
639-
('VULNERABILITY_ANALYSIS', 'Provides all abilities to make analysis decisions on vulnerabilities'),
640-
('VULNERABILITY_ANALYSIS_CREATE', 'Provides the ability to upload supported VEX documents to a project'),
641-
('VULNERABILITY_ANALYSIS_READ', 'Provides the ability read the VEX document for a project'),
642-
('VULNERABILITY_ANALYSIS_UPDATE', 'Provides the ability to make analysis decisions on vulnerabilities and upload supported VEX documents for a project'),
643-
('VIEW_POLICY_VIOLATION', 'Provides the ability to view policy violations'),
644-
('VULNERABILITY_MANAGEMENT', 'Allows all management permissions of internally-defined vulnerabilities'),
645-
('VULNERABILITY_MANAGEMENT_CREATE', 'Allows creation of internally-defined vulnerabilities'),
646-
('VULNERABILITY_MANAGEMENT_READ', 'Allows reading internally-defined vulnerabilities'),
647-
('VULNERABILITY_MANAGEMENT_UPDATE', 'Allows updating internally-defined vulnerabilities and vulnerability tags'),
648-
('VULNERABILITY_MANAGEMENT_DELETE', 'Allows management of internally-defined vulnerabilities'),
649-
('POLICY_VIOLATION_ANALYSIS', 'Provides the ability to make analysis decisions on policy violations'),
650-
('ACCESS_MANAGEMENT', 'Allows the management of users, teams, and API keys'),
651-
('ACCESS_MANAGEMENT_CREATE', 'Allows create permissions of users, teams, and API keys'),
652-
('ACCESS_MANAGEMENT_READ', 'Allows read permissions of users, teams, and API keys'),
653-
('ACCESS_MANAGEMENT_UPDATE', 'Allows update permissions of users, teams, and API keys'),
654-
('ACCESS_MANAGEMENT_DELETE', 'Allows delete permissions of users, teams, and API keys'),
655-
('SECRET_MANAGEMENT', 'Grants full secret management access'),
656-
('SECRET_MANAGEMENT_CREATE', 'Grants the ability to create secrets'),
657-
('SECRET_MANAGEMENT_UPDATE', 'Grants the ability to update secrets'),
658-
('SECRET_MANAGEMENT_DELETE', 'Grants the ability to delete secrets'),
659-
('SYSTEM_CONFIGURATION', 'Allows all access to configuration of the system including notifications, repositories, and email settings'),
660-
('SYSTEM_CONFIGURATION_CREATE', 'Allows creating configuration of the system including notifications, repositories, and email settings'),
661-
('SYSTEM_CONFIGURATION_READ', 'Allows reading the configuration of the system including notifications, repositories, and email settings'),
662-
('SYSTEM_CONFIGURATION_UPDATE', 'Allows updating the configuration of the system including notifications, repositories, and email settings'),
663-
('SYSTEM_CONFIGURATION_DELETE', 'Allows deleting the configuration of the system including notifications, repositories, and email settings'),
664-
('PROJECT_CREATION_UPLOAD', 'Provides the ability to optionally create project (if non-existent) on BOM or scan upload'),
665-
('POLICY_MANAGEMENT', 'Allows the creation, modification, and deletion of policy'),
666-
('POLICY_MANAGEMENT_CREATE', 'Allows the creation of a policy'),
667-
('POLICY_MANAGEMENT_READ', 'Allows reading of policies'),
668-
('POLICY_MANAGEMENT_UPDATE', 'Allows the modification of a policy'),
669-
('POLICY_MANAGEMENT_DELETE', 'Allows the deletion of a policy'),
670-
('TAG_MANAGEMENT', 'Allows the modification and deletion of tags'),
671-
('TAG_MANAGEMENT_DELETE', 'Allows the deletion of a tag')
672-
ON CONFLICT ("NAME") DO NOTHING;
673623
DROP TABLE IF EXISTS "%1$s".permission_name_map;
674624
CREATE UNLOGGED TABLE "%1$s".permission_name_map (
675625
orig_id BIGINT NOT NULL PRIMARY KEY

support/v4-migrator/src/main/java/org/dependencytrack/v4migrator/cli/BootstrapCommand.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import org.dependencytrack.migration.MigrationExecutor;
2222
import org.dependencytrack.v4migrator.ExitCode;
23+
import org.dependencytrack.v4migrator.PermissionCatalog;
2324
import org.dependencytrack.v4migrator.config.Connections;
2425
import org.dependencytrack.v4migrator.preflight.Preflight;
2526
import org.dependencytrack.v4migrator.preflight.Preflight.Mode;
@@ -59,6 +60,13 @@ protected int execute(final Jdbi target) {
5960
head, Preflight.EXPECTED_FLYWAY_HEAD);
6061
return ExitCode.SCHEMA_VERSION_MISMATCH;
6162
}
63+
64+
// Seed the v5 PERMISSION catalog now so that downstream load phases can FK-resolve
65+
// into it without depending on transform having run in the same invocation. See
66+
// PermissionCatalog for context.
67+
LOGGER.info("Seeding v5 PERMISSION catalog");
68+
PermissionCatalog.seed(target);
69+
6270
LOGGER.info("Bootstrap complete. Flyway head = {}. Run 'extract' or 'run' next.", head);
6371
return ExitCode.OK;
6472
}
@@ -67,5 +75,6 @@ protected int execute(final Jdbi target) {
6775
protected void printPlan() {
6876
System.out.println(" Phase: bootstrap");
6977
System.out.println(" Target Flyway head to apply: " + Preflight.EXPECTED_FLYWAY_HEAD);
78+
System.out.println(" Seed v5 PERMISSION catalog: yes (idempotent)");
7079
}
7180
}

support/v4-migrator/src/main/java/org/dependencytrack/v4migrator/preflight/Preflight.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,8 +196,8 @@ private void checkFlywayHead(final List<String> failures) {
196196

197197
/**
198198
* Refuse to operate against a v5 cluster that already has user data.
199-
* Checks a small set of high-trust tables. Flyway-installed seed data
200-
* (e.g. default permissions) is permitted.
199+
* Checks a small set of high-trust tables. Seed data populated by bootstrap
200+
* (default permissions) is permitted.
201201
*/
202202
private void checkTargetEmpty(final List<String> failures) {
203203
final String[] mustBeEmpty = {

support/v4-migrator/src/test/java/org/dependencytrack/v4migrator/BootstrapIT.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,13 +124,61 @@ void bootstrapAppliesFlywayUpToExpectedHead() {
124124
.one());
125125
assertThat(head).isEqualTo(Preflight.EXPECTED_FLYWAY_HEAD);
126126

127+
// PERMISSION catalog must be seeded by bootstrap so that downstream load phases
128+
// can FK-resolve permission IDs even if the operator follows the documented
129+
// "drop v5 schema, re-bootstrap, re-run load" recovery (issue #6217).
130+
final long permissionCount = jdbi().withHandle(h ->
131+
h.createQuery("SELECT count(*) FROM \"PERMISSION\"").mapTo(Long.class).one());
132+
assertThat(permissionCount).isEqualTo(42L);
133+
final boolean hasV5OnlyPermission = jdbi().withHandle(h ->
134+
h.createQuery("""
135+
SELECT EXISTS (
136+
SELECT 1 FROM "PERMISSION"
137+
WHERE "NAME" = 'PORTFOLIO_ACCESS_CONTROL_BYPASS')
138+
""")
139+
.mapTo(Boolean.class).one());
140+
assertThat(hasV5OnlyPermission).isTrue();
141+
127142
// Default-mode preflight now passes (schema applied, no user data, no PERMISSION pre-seed needed).
128143
final PreflightResult after = new Preflight(jdbi(), null, opts, Mode.DEFAULT).run();
129144
assertThat(after.ok())
130145
.as("default preflight should pass after bootstrap; failures: %s", after.failures())
131146
.isTrue();
132147
}
133148

149+
@Test
150+
@Order(4)
151+
void shouldRemainIdempotentWhenBootstrapInvokedTwice() {
152+
final GlobalOptions opts = optsForContainer();
153+
opts.stagingSchema = "dt_v4_migration_bootstrap";
154+
155+
final long countBefore = jdbi().withHandle(h ->
156+
h.createQuery("SELECT count(*) FROM \"PERMISSION\"").mapTo(Long.class).one());
157+
158+
final ByteArrayOutputStream capture = new ByteArrayOutputStream();
159+
final PrintStream origOut = System.out;
160+
final PrintStream origErr = System.err;
161+
System.setOut(new PrintStream(capture, true));
162+
System.setErr(new PrintStream(capture, true));
163+
final int exit;
164+
try {
165+
exit = new CommandLine(new V4Migrator()).execute(
166+
"bootstrap",
167+
"--target-url", container.getJdbcUrl(),
168+
"--target-user", container.getUsername(),
169+
"--target-pass", container.getPassword(),
170+
"--staging-schema", opts.stagingSchema);
171+
} finally {
172+
System.setOut(origOut);
173+
System.setErr(origErr);
174+
}
175+
assertThat(exit).as("second bootstrap output: %s", capture).isEqualTo(ExitCode.OK);
176+
177+
final long countAfter = jdbi().withHandle(h ->
178+
h.createQuery("SELECT count(*) FROM \"PERMISSION\"").mapTo(Long.class).one());
179+
assertThat(countAfter).isEqualTo(countBefore);
180+
}
181+
134182
private GlobalOptions optsForContainer() {
135183
final GlobalOptions opts = new GlobalOptions();
136184
opts.targetUrl = container.getJdbcUrl();

0 commit comments

Comments
 (0)