Skip to content

Add secrets CRUD API and template rendering with masking support#1275

Draft
ikhoon wants to merge 3 commits intoline:mainfrom
ikhoon:template-secret
Draft

Add secrets CRUD API and template rendering with masking support#1275
ikhoon wants to merge 3 commits intoline:mainfrom
ikhoon:template-secret

Conversation

@ikhoon
Copy link
Copy Markdown
Contributor

@ikhoon ikhoon commented Mar 11, 2026

Motivation:

Add support for managing secrets that can be injected into templates. Secrets differ from variables in that their values are masked for security:

  • list APIs always mask
  • GET/create/update APIs mask for non-system-admin users

When rendered in templates:

  • secrets are shown only to app-identity or system-admin users
  • session-based (web UI) users see ****

Modifications:

  • Add SecretServiceV1 with project-level and repo-level CRUD endpoints
  • Add Secret model class with withoutValue() masking support
  • Update Templater to merge secrets into the FreeMarker data model via syntax
  • Update ContentServiceV1 to pass User through template rendering paths
  • Rename package from 'variable' to 'template' for better organization
  • Add SecretServiceV1Test covering CRUD, masking, and role permissions
  • Add SecretTemplateCrudTest covering template rendering with secrets including session-cookie masking verification

Result:

Users can now store secrets at project and repo levels and reference them in templates.

TODO:

  • Add Web UI for managing secrets at the project and repository levels
  • Update the template and variable documentation

Motivation:

Add support for managing secrets that can be injected into templates.
Secrets differ from variables in that their values are masked
for security:
- list APIs always mask
- GET/create/update APIs mask for non-system-admin users.

When rendered in templates:
- secrets are shown only to app-identity or system-admin users
- session-based (web UI) users see "****".

Modifications:

- Add `SecretServiceV1` with project-level and repo-level CRUD endpoints
- Add `Secret` model class with withoutValue() masking support
- Update `Templater` to merge secrets into the FreeMarker data model via
  syntax.
- Update `ContentServiceV1` to pass User through template rendering paths
- Rename package from 'variable' to 'template' for better organization
- Add `SecretServiceV1Test` covering CRUD, masking, and role permissions
- Add `SecretTemplateCrudTest` covering template rendering with secrets
  including session-cookie masking verification

Result:

- Users can now store secrets at project and repo levels and reference
  them in templates.

TODO:

- Add Web UI for managing secrets at the project and repository levels.
@ikhoon ikhoon added this to the 0.81.0 milestone Mar 11, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 11, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 911865d0-dda5-4674-88ff-5b14667307a9

📥 Commits

Reviewing files that changed from the base of the PR and between 4ab5d17 and d6666c0.

📒 Files selected for processing (1)
  • server/src/test/java/com/linecorp/centraldogma/server/SecretTemplateCrudTest.java

📝 Walkthrough

Walkthrough

Adds a Secret model and SecretServiceV1 (project/repo CRUD, RBAC, masking), moves variable-related classes into an api.template package, updates templating and content services to fetch/mask secrets alongside variables, and adds comprehensive tests and related wiring changes.

Changes

Cohort / File(s) Summary
Core Secret API
server/src/main/java/com/linecorp/centraldogma/server/internal/api/template/Secret.java, server/src/main/java/com/linecorp/centraldogma/server/internal/api/template/SecretServiceV1.java
New Secret data model and SecretServiceV1 added. Provides project- and repo-scoped CRUD endpoints, RBAC annotations, metadata handling, path/ID validation, and value-masking for non-admins.
Template Retrieval & Rendering
server/src/main/java/com/linecorp/centraldogma/server/internal/api/template/Templater.java, server/src/main/java/com/linecorp/centraldogma/server/internal/api/ContentServiceV1.java
Templater now accepts User, adds secret and variable CRUD repos, performs parallel fetches of variables and secrets, introduces mergeSecrets and maybeMaskSecret, and threads secrets into template processing. ContentServiceV1 signatures updated to pass User.
Variable Package Migration
server/src/main/java/com/linecorp/centraldogma/server/internal/api/template/Variable.java, server/src/main/java/com/linecorp/centraldogma/server/internal/api/template/VariableType.java, server/src/main/java/com/linecorp/centraldogma/server/internal/api/template/package-info.java
Moved Variable and VariableType (and package-info) from api.variable to api.template (package rename only).
VariableService Adjustments
server/src/main/java/com/linecorp/centraldogma/server/internal/api/template/VariableServiceV1.java
Replaced former public crudContext API with new variableCrudContext helpers (visibility/name changes) and updated usages; includes an accidental typo-named overload (varaibleCrudContext).
HTTP Wiring
server/src/main/java/com/linecorp/centraldogma/server/CentralDogma.java
Registers SecretServiceV1 into the HTTP V1 API surface and updates imports to reference services from the template package.
Tests
server/src/test/java/com/linecorp/centraldogma/server/SecretTemplateCrudTest.java, server/src/test/java/com/linecorp/centraldogma/server/internal/api/template/SecretServiceV1Test.java, server/src/test/java/com/linecorp/centraldogma/server/VariableTemplateCrudTest.java, server/src/test/java/com/linecorp/centraldogma/server/internal/api/template/VariableServiceV1Test.java
Adds comprehensive secret-related tests (CRUD, RBAC, masking, template rendering) and updates variable-related tests for package moves.
Repo metadata
.gitignore
Adds entries to ignore Claude local settings (.claude/settings.local.json).

Sequence Diagram(s)

sequenceDiagram
    actor Client
    participant SecretServiceV1
    participant Authorizer
    participant CrudOperation
    Client->>SecretServiceV1: createSecret(project, secret, author, user)
    SecretServiceV1->>Authorizer: check permissions
    alt allowed
        Authorizer-->>SecretServiceV1: allowed
        SecretServiceV1->>CrudOperation: persist secret (with name/creation)
        CrudOperation-->>SecretServiceV1: HasRevision<Secret>
        alt user is admin
            SecretServiceV1-->>Client: return secret (real value)
        else
            SecretServiceV1->>SecretServiceV1: mask value ("****")
            SecretServiceV1-->>Client: return secret (masked)
        end
    else denied
        Authorizer-->>SecretServiceV1: denied
        SecretServiceV1-->>Client: HTTP 403
    end
Loading
sequenceDiagram
    actor Client
    participant ContentServiceV1
    participant Templater
    participant VariableServiceV1
    participant SecretServiceV1
    Client->>ContentServiceV1: getFiles(repo, path, user)
    ContentServiceV1->>Templater: render(repo, entry, variableFile, revision, user)
    par fetch variables and secrets
        Templater->>VariableServiceV1: list project/repo variables
        VariableServiceV1-->>Templater: List<HasRevision<Variable>>
    and
        Templater->>SecretServiceV1: list project/repo secrets
        SecretServiceV1-->>Templater: List<HasRevision<Secret>>
    end
    Templater->>Templater: maybeMaskSecret(secret, user)
    Templater->>Templater: merge variables + secrets into data model
    Templater->>Templater: process template with combined data
    Templater-->>ContentServiceV1: rendered Entry
    ContentServiceV1-->>Client: HTTP 200 with rendered content
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Suggested reviewers

  • trustin
  • jrhee17

Poem

🐰 I hopped through code and hid each key with care,

Admins may peek while others find air,
Secrets tucked in templates, variables sing,
New services sprout and tests dance in a ring,
🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.39% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add secrets CRUD API and template rendering with masking support' clearly and specifically summarizes the main change in the PR, which is adding secret management functionality with masking capabilities.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, explaining the motivation, modifications, and results of adding secrets CRUD API and template rendering with proper masking support.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (4)
server/src/main/java/com/linecorp/centraldogma/server/internal/api/template/Secret.java (1)

105-117: Rename local variable from variable to secret for consistency.

The local variable is named variable but should be secret to match the class name and improve readability.

📝 Proposed fix
     `@Override`
     public boolean equals(Object o) {
         if (!(o instanceof Secret)) {
             return false;
         }

-        final Secret variable = (Secret) o;
-        return id.equals(variable.id) &&
-               Objects.equals(name, variable.name) &&
-               value.equals(variable.value) &&
-               Objects.equals(description, variable.description) &&
-               Objects.equals(creation, variable.creation);
+        final Secret secret = (Secret) o;
+        return id.equals(secret.id) &&
+               Objects.equals(name, secret.name) &&
+               value.equals(secret.value) &&
+               Objects.equals(description, secret.description) &&
+               Objects.equals(creation, secret.creation);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@server/src/main/java/com/linecorp/centraldogma/server/internal/api/template/Secret.java`
around lines 105 - 117, In the Secret.equals method, rename the local variable
currently declared as "variable" to "secret" for clarity and consistency with
the Secret class; update the cast line and all subsequent uses (the local
variable in the equals method) so the code reads: final Secret secret = (Secret)
o; and compare against secret instead of variable.
server/src/main/java/com/linecorp/centraldogma/server/internal/api/template/package-info.java (1)

17-21: Update package Javadoc to reflect the broader scope.

The Javadoc says "An API for managing variables" but this package now also manages secrets and template rendering. Consider updating the description to reflect the expanded scope.

📝 Suggested Javadoc update
 /**
- * An API for managing variables.
+ * An API for managing variables, secrets, and template rendering.
  */
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@server/src/main/java/com/linecorp/centraldogma/server/internal/api/template/package-info.java`
around lines 17 - 21, Update the package Javadoc in package-info.java for
package com.linecorp.centraldogma.server.internal.api.template to describe its
broader responsibilities (managing variables, secrets, and template rendering)
instead of the current "An API for managing variables"; replace the single-line
description with a concise summary that mentions variables, secrets, and
template rendering and keep the existing annotations (e.g., `@NullMarked`) intact.
server/src/test/java/com/linecorp/centraldogma/server/SecretTemplateCrudTest.java (1)

324-358: Cover the app-identity rendering branch too.

This test only contrasts a system-admin client with a session cookie. The new masking behavior also has a distinct app-identity path, so a regression that masks app-token renders would still pass here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@server/src/test/java/com/linecorp/centraldogma/server/SecretTemplateCrudTest.java`
around lines 324 - 358, The test secretsMaskedWhenAccessedViaSessionCookie only
verifies token-based and session-cookie rendering paths; add a similar assertion
for the app-identity rendering branch to prevent regressions that affect
app-token rendering. After pushing the template and before finishing the test,
create or obtain an app-identity client (e.g., reuse or implement a helper like
createAppIdentityClient or obtain an app token) and fetch the same contents path
with renderTemplate=true using that client; then assert the HTTP response is OK
and that both "proj" and "repo" are masked as "****" just like the session-based
assertions, mirroring the token/session checks around
testRepo1.file(...).renderTemplate(true) and sessionClient.get(...).
server/src/test/java/com/linecorp/centraldogma/server/internal/api/template/SecretServiceV1Test.java (1)

59-62: Add a non-admin write-capable client to this fixture.

Only MEMBER and outsider app tokens are created here, so the contract that create/update responses are masked for non-system-admin users never gets exercised. A non-admin project OWNER / repo WRITER client would close that gap.

Also applies to: 86-112

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@server/src/test/java/com/linecorp/centraldogma/server/internal/api/template/SecretServiceV1Test.java`
around lines 59 - 62, Add a non-admin write-capable client to the
SecretServiceV1Test fixture by declaring a new BlockingWebClient (e.g.,
writerClient) alongside memberClient and outsiderClient, then initialize it in
the same setup block where the MEMBER and outsider tokens are created by
generating a project OWNER or repo WRITER token (use the same token-creation
helper used for the MEMBER token) and building a BlockingWebClient with that
token; ensure writerClient is used in the tests that exercise create/update
responses for non-system-admin users so the masked responses for non-admin
writers are covered.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@server/src/main/java/com/linecorp/centraldogma/server/internal/api/template/Templater.java`:
- Around line 19-21: In Templater.java update the bad import of
VariableServiceV1.varaibleCrudContext to the correctly spelled
VariableServiceV1.variableCrudContext; remove the misspelled import line and
ensure any usages reference variableCrudContext (and not the old typo), keeping
the existing import of SecretServiceV1.secretCrudContext unchanged so all
references compile against VariableServiceV1.variableCrudContext.

In
`@server/src/main/java/com/linecorp/centraldogma/server/internal/api/template/VariableServiceV1.java`:
- Around line 258-260: Rename the static method currently named
varaibleCrudContext to variableCrudContext in VariableServiceV1 (so the
signature becomes static CrudContext variableCrudContext(String projectName,
Revision revision)), and update any call sites and imports (notably the import
in Templater.java that references the old name) to use the corrected name;
ensure there are no other references to the misspelled method remaining.

In
`@server/src/test/java/com/linecorp/centraldogma/server/SecretTemplateCrudTest.java`:
- Around line 426-443: The helper createVariable currently constructs JSON by
string interpolation which breaks for ids/values containing quotes or
backslashes; instead instantiate a Variable (or a POJO with id/type/value) and
pass it to the HTTP request serializer (use .contentJson(variable) on the
request) so the body is properly JSON-escaped; keep the same path selection
logic and assert CREATED as before, but replace the manual json string and
.content(MediaType.JSON, json) call with building the Variable object and
calling .contentJson(variable) on the request prepared by httpClient in
createVariable.

---

Nitpick comments:
In
`@server/src/main/java/com/linecorp/centraldogma/server/internal/api/template/package-info.java`:
- Around line 17-21: Update the package Javadoc in package-info.java for package
com.linecorp.centraldogma.server.internal.api.template to describe its broader
responsibilities (managing variables, secrets, and template rendering) instead
of the current "An API for managing variables"; replace the single-line
description with a concise summary that mentions variables, secrets, and
template rendering and keep the existing annotations (e.g., `@NullMarked`) intact.

In
`@server/src/main/java/com/linecorp/centraldogma/server/internal/api/template/Secret.java`:
- Around line 105-117: In the Secret.equals method, rename the local variable
currently declared as "variable" to "secret" for clarity and consistency with
the Secret class; update the cast line and all subsequent uses (the local
variable in the equals method) so the code reads: final Secret secret = (Secret)
o; and compare against secret instead of variable.

In
`@server/src/test/java/com/linecorp/centraldogma/server/internal/api/template/SecretServiceV1Test.java`:
- Around line 59-62: Add a non-admin write-capable client to the
SecretServiceV1Test fixture by declaring a new BlockingWebClient (e.g.,
writerClient) alongside memberClient and outsiderClient, then initialize it in
the same setup block where the MEMBER and outsider tokens are created by
generating a project OWNER or repo WRITER token (use the same token-creation
helper used for the MEMBER token) and building a BlockingWebClient with that
token; ensure writerClient is used in the tests that exercise create/update
responses for non-system-admin users so the masked responses for non-admin
writers are covered.

In
`@server/src/test/java/com/linecorp/centraldogma/server/SecretTemplateCrudTest.java`:
- Around line 324-358: The test secretsMaskedWhenAccessedViaSessionCookie only
verifies token-based and session-cookie rendering paths; add a similar assertion
for the app-identity rendering branch to prevent regressions that affect
app-token rendering. After pushing the template and before finishing the test,
create or obtain an app-identity client (e.g., reuse or implement a helper like
createAppIdentityClient or obtain an app token) and fetch the same contents path
with renderTemplate=true using that client; then assert the HTTP response is OK
and that both "proj" and "repo" are masked as "****" just like the session-based
assertions, mirroring the token/session checks around
testRepo1.file(...).renderTemplate(true) and sessionClient.get(...).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 322cd3ca-892e-4d4a-986d-f848bf7e3f83

📥 Commits

Reviewing files that changed from the base of the PR and between 3b17ec4 and 96295a5.

📒 Files selected for processing (13)
  • server/src/main/java/com/linecorp/centraldogma/server/CentralDogma.java
  • server/src/main/java/com/linecorp/centraldogma/server/internal/api/ContentServiceV1.java
  • server/src/main/java/com/linecorp/centraldogma/server/internal/api/template/Secret.java
  • server/src/main/java/com/linecorp/centraldogma/server/internal/api/template/SecretServiceV1.java
  • server/src/main/java/com/linecorp/centraldogma/server/internal/api/template/Templater.java
  • server/src/main/java/com/linecorp/centraldogma/server/internal/api/template/Variable.java
  • server/src/main/java/com/linecorp/centraldogma/server/internal/api/template/VariableServiceV1.java
  • server/src/main/java/com/linecorp/centraldogma/server/internal/api/template/VariableType.java
  • server/src/main/java/com/linecorp/centraldogma/server/internal/api/template/package-info.java
  • server/src/test/java/com/linecorp/centraldogma/server/SecretTemplateCrudTest.java
  • server/src/test/java/com/linecorp/centraldogma/server/VariableTemplateCrudTest.java
  • server/src/test/java/com/linecorp/centraldogma/server/internal/api/template/SecretServiceV1Test.java
  • server/src/test/java/com/linecorp/centraldogma/server/internal/api/template/VariableServiceV1Test.java

@ikhoon ikhoon modified the milestones: 0.81.0, 0.82.0 Mar 12, 2026
@ikhoon ikhoon modified the milestones: 0.82.0, 0.83.0 Apr 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant