Skip to content

Commit ded033b

Browse files
authored
Merge branch 'main' into fix-k8s-aggregator-editor-distinct
2 parents a5a7f83 + f8a443c commit ded033b

24 files changed

Lines changed: 1675 additions & 80 deletions

File tree

server/src/main/java/com/linecorp/centraldogma/server/internal/api/sysadmin/AppIdentityRegistryService.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,33 @@ public CompletableFuture<AppIdentity> updateAppIdentity(ServiceRequestContext ct
248248
});
249249
}
250250

251+
/**
252+
* POST /appIdentities/{appId}/secret
253+
*
254+
* <p>Regenerates the secret of the deactivated token of the specified {@code appId} and returns the
255+
* token with a newly-generated secret. The token must be deactivated first and the new secret does
256+
* not authenticate until the token is activated, so that the new secret can be distributed to the
257+
* clients before it takes effect.
258+
*/
259+
@Post("/appIdentities/{appId}/secret")
260+
public CompletableFuture<Token> regenerateTokenSecret(ServiceRequestContext ctx,
261+
@Param String appId,
262+
Author author, User loginUser) {
263+
// Permission is checked at the HTTP layer; the metadata layer is caller-agnostic.
264+
return getTokenOrRespondForbidden(ctx, appId, loginUser).thenCompose(
265+
token -> {
266+
if (token.isDeleted()) {
267+
throw new IllegalArgumentException(
268+
"You can't regenerate the secret of the token scheduled for deletion.");
269+
}
270+
if (token.isActive()) {
271+
throw new IllegalArgumentException(
272+
"You can't regenerate the secret of an active token. Deactivate it first.");
273+
}
274+
return mds.regenerateTokenSecret(author, appId);
275+
});
276+
}
277+
251278
/**
252279
* PATCH /appIdentities/{appId}/level
253280
*

server/src/main/java/com/linecorp/centraldogma/server/internal/storage/repository/git/TransformingChangesApplier.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ int doApply(Revision headRevision, DirCache dirCache,
7676
} catch (CentralDogmaException e) {
7777
throw e;
7878
} catch (Exception e) {
79-
throw new ChangeConflictException("failed to transform the content: " + oldJsonNode +
80-
" transformer: " + transformer, e);
79+
// Do not include the old content in the message; it may contain sensitive data such as secrets.
80+
throw new ChangeConflictException("failed to transform the content with " + transformer, e);
8181
}
8282
return 0;
8383
}

server/src/main/java/com/linecorp/centraldogma/server/metadata/AppIdentityService.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,56 @@ Revision purgeAppIdentity(Author author, String appId) {
168168
.join();
169169
}
170170

171+
CompletableFuture<Token> regenerateTokenSecret(Author author, String appId) {
172+
requireNonNull(author, "author");
173+
requireNonNull(appId, "appId");
174+
175+
final String commitSummary = "Regenerate the secret of the token: " + appId;
176+
177+
final AppIdentityRegistryTransformer transformer = new AppIdentityRegistryTransformer(
178+
(headRevision, registry) -> {
179+
final AppIdentity appIdentity = registry.get(appId); // Raise an exception if not found.
180+
if (appIdentity.deletion() != null) {
181+
// Note that a ChangeConflictException is raised instead of an
182+
// IllegalArgumentException so that the storage layer does not wrap it with
183+
// another exception.
184+
throw new ChangeConflictException(
185+
"The app identity is already destroyed: " + appId);
186+
}
187+
throwIfInvalidType(appId, appIdentity, AppIdentityType.TOKEN);
188+
if (appIdentity.deactivation() == null) {
189+
// Regenerating the secret of an active token would break its clients with no
190+
// way to prepare, so the token must be deactivated first.
191+
throw new ChangeConflictException(
192+
"The token must be deactivated before regenerating its secret: " + appId);
193+
}
194+
195+
final Token token = (Token) appIdentity;
196+
final String newSecret = SECRET_PREFIX + UUID.randomUUID();
197+
if (registry.secrets().containsKey(newSecret)) {
198+
throw new ChangeConflictException("Secret already exists");
199+
}
200+
201+
final Token newToken = new Token(token.appId(), newSecret, token.isSystemAdmin(),
202+
token.allowGuestAccess(), token.creation(),
203+
token.deactivation(), null);
204+
final Map<String, AppIdentity> newAppIds =
205+
updateMap(registry.appIds(), appId, newToken);
206+
// A deactivated token has no entry in the secret map; create a new map so that
207+
// the new registry does not share the mutable map with the old one.
208+
return new AppIdentityRegistry(newAppIds, ImmutableMap.copyOf(registry.secrets()),
209+
registry.certificateIds());
210+
});
211+
// Read the registry back at the revision this commit produced so that the caller gets
212+
// the secret of this commit even if another commit lands right after.
213+
return appIdentityRegistryRepo.push(INTERNAL_PROJECT_DOGMA, Project.REPO_DOGMA, author,
214+
commitSummary, transformer)
215+
.thenCompose(revision -> appIdentityRegistryRepo.fetch(
216+
INTERNAL_PROJECT_DOGMA, Project.REPO_DOGMA, TOKEN_JSON,
217+
revision))
218+
.thenApply(holder -> (Token) holder.object().get(appId));
219+
}
220+
171221
CompletableFuture<Revision> activateToken(Author author, String appId) {
172222
requireNonNull(author, "author");
173223
requireNonNull(appId, "appId");

server/src/main/java/com/linecorp/centraldogma/server/metadata/MetadataService.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1165,6 +1165,16 @@ public CompletableFuture<Revision> deactivateToken(Author author, String appId)
11651165
return appIdentityService.deactivateToken(author, appId);
11661166
}
11671167

1168+
/**
1169+
* Regenerates the secret of the deactivated {@link Token} of the specified {@code appId} and
1170+
* returns the {@link Token} with the newly-generated secret. The token must be deactivated first
1171+
* and the new secret does not authenticate until the token is activated. The regeneration fails
1172+
* with a {@link ChangeConflictException} if the token is still active.
1173+
*/
1174+
public CompletableFuture<Token> regenerateTokenSecret(Author author, String appId) {
1175+
return appIdentityService.regenerateTokenSecret(author, appId);
1176+
}
1177+
11681178
/**
11691179
* Returns an {@link AppIdentity} which has the specified {@code appId}.
11701180
*/

server/src/main/java/com/linecorp/centraldogma/server/metadata/RepositorySupport.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,13 @@ CompletableFuture<HolderWithRevision<T>> fetch(String projectName, String repoNa
6666
return fetch(projectManager().get(projectName).repos().get(repoName), path);
6767
}
6868

69+
CompletableFuture<HolderWithRevision<T>> fetch(String projectName, String repoName, String path,
70+
Revision revision) {
71+
requireNonNull(projectName, "projectName");
72+
requireNonNull(repoName, "repoName");
73+
return fetch(projectManager().get(projectName).repos().get(repoName), path, revision);
74+
}
75+
6976
private CompletableFuture<HolderWithRevision<T>> fetch(Repository repository, String path) {
7077
requireNonNull(path, "path");
7178
final Revision revision = normalize(repository);

0 commit comments

Comments
 (0)