Skip to content

Introduces plugins.security.superadmin.secret for superadmin access when https is disabled#6073

Open
itsmevichu wants to merge 6 commits intoopensearch-project:mainfrom
itsmevichu:feature/gh-6051
Open

Introduces plugins.security.superadmin.secret for superadmin access when https is disabled#6073
itsmevichu wants to merge 6 commits intoopensearch-project:mainfrom
itsmevichu:feature/gh-6051

Conversation

@itsmevichu
Copy link
Copy Markdown
Contributor

@itsmevichu itsmevichu commented Apr 6, 2026

Description

  • New feature
  • Why these changes are required?
    • These changes add support for a superadmin secret authentication path, allowing superadmin access without relying on admin DN or the security index. This creates a recovery and operability gap, especially if the security index is deleted, corrupted, or otherwise unavailable.
  • What is the old behavior before changes and new behavior after changes?

Old Behavior

  • No dedicated superadmin secret existed.
  • Superadmin access was only possible via:
    • admin DN client certificate authentication, or
    • existing security index-based authentication flows.
  • In HTTP-only mode, there was no equivalent recovery or superadmin bypass path, because DN-based superadmin only works for HTTPS/client-cert auth.

New Behaviour

  • Introduces a new setting plugins.security.superadmin.secret for plaintext secret configuration.
  • Adds a corresponding secure variant- plugins.security.superadmin.secret_secure for keystore-based storage, which is the recommended approach:
    • ./bin/opensearch-keystore add plugins.security.superadmin.secret_secure
  • At runtime, the secret is validated from the incoming request header, when the secret is valid, the request is authenticated as the superadmin user and receives full superadmin capabilities.
    • X-OpenSearch-Superadmin-Secret
  • superadmin.sh cnow supports the secret via configurable options:
    • -sas or --superadmin-secret

Issues Resolved

#6051

To do:

  • Unit tests
  • Docs

Testing

  • Manual testing

Check List

  • New functionality includes testing
  • New functionality has been documented
  • New Roles/Permissions have a corresponding security dashboards plugin PR
  • API changes companion pull request created
  • Commits are signed per the DCO using --signoff

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.

@cwperks
Copy link
Copy Markdown
Member

cwperks commented Apr 6, 2026

@itsmevichu wherever possible we should abstract AdminsDn.isAdmin and support both 1) connecting with admin cert or 2) specifying the superadmin secret.

Can you also include in the PR description how a caller uses the superadmin secret to make a request to the cluster?

@itsmevichu
Copy link
Copy Markdown
Contributor Author

Sure, will do the changes.

Comment thread src/main/java/org/opensearch/security/support/SecuritySettings.java Outdated
public static final Setting<SecureString> SECURITY_SUPERADMIN_SECRET_SETTING = SecureSetting.secureString(
ConfigConstants.SECURITY_SUPERADMIN_SECRET_SECURE,
SECURITY_SUPERADMIN_SECRET_INSECURE_SETTING
);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

IMHO, this should be accompanied with a very prominent note that using such a setting is inherently insecure and should be only if there is no other way. It should be noted that using TLS client cert auth should be always preferred.

Comment thread src/main/java/org/opensearch/security/auth/BackendRegistry.java Outdated
*/
boolean userIsSuperAdmin(User user, AdminDNs adminDNs) {
return user != null && adminDNs.isAdmin(user);
return user != null && (adminDNs.isAdmin(user) || ConfigConstants.SECURITY_SUPERADMIN_SECRET_USER.equals(user.getName()));
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What happens if I configure a user on my cluster with the same name as this hard coded SECURITY_SUPERADMIN_SECRET_USER? Is there a path to escalate privileges here?

Comment thread src/main/java/org/opensearch/security/auth/BackendRegistry.java Outdated
*/
if (!gRPC) {
final String providedSecret = request.header(ConfigConstants.SECURITY_SUPERADMIN_SECRET_HEADER);
if (isSuperadminSecretValid(providedSecret)) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Do we want to audit log on a failed check since this is similar to a failed basic auth attempt?

Signed-off-by: Vishnutheep B <vishnutheep@gmail.com>
Signed-off-by: Vishnutheep B <vishnutheep@gmail.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 10, 2026

PR Code Analyzer ❗

AI-powered 'Code-Diff-Analyzer' found issues on commit 9a27345.

PathLineSeverityDescription
src/main/java/org/opensearch/security/configuration/SuperAdminAuthority.java49highIntroduces a new superadmin authentication path via the HTTP header 'X-OpenSearch-Superadmin-Secret' that bypasses the existing TLS client certificate requirement. Any client that knows or obtains this secret gains full superadmin privileges over the cluster. While the comparison uses MessageDigest.isEqual (constant-time), the mechanism itself represents a new credential-based backdoor: if the configured secret is weak, leaked, or brute-forced, an attacker with network access gains unrestricted control without needing a PKI certificate.
src/main/java/org/opensearch/security/tools/SecurityAdmin.java303mediumThe new '--superadmin-secret' / '-sas' CLI option accepts the superadmin secret as a command-line argument. Command-line arguments are visible to all local users via process listings (e.g., 'ps aux'), shell history files, and system audit logs, exposing the credential to any user or process with access to the host.
src/main/java/org/opensearch/security/support/SecuritySettings.java62mediumSECURITY_SUPERADMIN_SECRET_INSECURE_SETTING registers the superadmin secret under ConfigConstants.SECURITY_SUPERADMIN_SECRET as an insecure (plaintext) OpenSearch setting. This permits the high-privilege secret to be stored in unencrypted opensearch.yml configuration files or passed through environment variables, increasing its exposure surface significantly compared to the secure keystore path.
src/main/java/org/opensearch/security/support/ConfigConstants.java432lowIntroduces the internal synthetic username '_opendistro_security_superadmin_secret_user_' which is granted superadmin rights in all isSuperAdmin checks. This special username bypasses normal admin DN checks and cannot be distinguished from a legitimate user by name alone; any code that creates a User object with this exact name would silently receive superadmin authority.

The table above displays the top 10 most important findings.

Total: 4 | Critical: 0 | High: 1 | Medium: 2 | Low: 1


Pull Requests Author(s): Please update your Pull Request according to the report above.

Repository Maintainer(s): You can bypass diff analyzer by adding label skip-diff-analyzer after reviewing the changes carefully, then re-run failed actions. To re-enable the analyzer, remove the label, then re-run all actions.


⚠️ Note: The Code-Diff-Analyzer helps protect against potentially harmful code patterns. Please ensure you have thoroughly reviewed the changes beforehand.

Thanks.

@github-actions
Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

🧪 No relevant tests
🔒 Security concerns

Sensitive information exposure:
The superadmin secret is stored in settings and can be configured via an insecure plaintext setting (plugins.security.superadmin.secret). If this setting is used instead of the secure keystore variant, the secret will appear in configuration files, logs, or cluster state APIs that expose settings. The PR description recommends the secure variant, but the insecure fallback remains available and could be accidentally used, exposing the secret.

✅ No TODO sections
🔀 No multiple PR themes
⚡ Recommended focus areas for review

Timing Attack

The secret comparison uses MessageDigest.isEqual, which is constant-time. However, the method first checks if either string is empty using ObjectUtils.isEmpty before performing the comparison. If the configured secret is empty, the method returns false immediately without performing the constant-time comparison. An attacker can distinguish between "no secret configured" and "wrong secret provided" by measuring response times, potentially leaking whether the feature is enabled.

private boolean isSuperadminSecretValid(final String providedSecret) {
    if (ObjectUtils.isEmpty(superadminSecret) || ObjectUtils.isEmpty(providedSecret)) {
        return false;
    }

    try {
        return MessageDigest.isEqual(
            superadminSecret.getBytes(StandardCharsets.UTF_8),
            providedSecret.getBytes(StandardCharsets.UTF_8)
        );
    } catch (Exception e) {
        log.debug("Error comparing superadmin secret", e);
        return false;
    }
Authentication Bypass

When superadmin secret authentication fails (hasSecretHeader returns true but isAdminViaSecret returns false), the code logs a failed login with securityadmin=false and then continues to the normal authentication flow. This allows an attacker who provides an invalid secret to fall back to other authentication methods. The intended behavior is unclear, but if the secret header presence should block fallback authentication, this is a bypass. If fallback is intended, the audit log entry is misleading because it suggests the request was not a superadmin attempt.

} else if (!gRPC && superAdminAuthority.hasSecretHeader(request)) {
    // Failed superadmin secret authentication attempt
    auditLog.logFailedLogin("superadmin", false, null, request);
}
Possible Issue

The isSuperadminSecretValid method catches all exceptions during secret comparison and logs them at debug level, then returns false. If an unexpected exception occurs (e.g., charset encoding issue), the failure is silent except for debug logs. In production with info-level logging, administrators will not be alerted to the failure, and superadmin access will be silently denied without clear indication of the cause.

private boolean isSuperadminSecretValid(final String providedSecret) {
    if (ObjectUtils.isEmpty(superadminSecret) || ObjectUtils.isEmpty(providedSecret)) {
        return false;
    }

    try {
        return MessageDigest.isEqual(
            superadminSecret.getBytes(StandardCharsets.UTF_8),
            providedSecret.getBytes(StandardCharsets.UTF_8)
        );
    } catch (Exception e) {
        log.debug("Error comparing superadmin secret", e);
        return false;
    }
}

@github-actions
Copy link
Copy Markdown
Contributor

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix authentication bypass for secret admin

When isSecretAdmin is true but user is null, the authentication should still
proceed. The current logic may incorrectly reject valid superadmin secret
authentication if the user object hasn't been set in thread context yet.

src/main/java/org/opensearch/security/rest/SecurityConfigUpdateAction.java [95-105]

 final boolean isSecretAdmin = superAdminAuthority.isAdminViaSecret(SecurityRequestFactory.from(request));
 
 if (sslInfo == null && !isSecretAdmin) {
     return channel -> channel.sendResponse(new BytesRestResponse(RestStatus.FORBIDDEN, ""));
 }
 
 final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER);
 
 // only allowed for admins
-if (user == null || !superAdminAuthority.isSuperAdmin(user)) {
+if (!isSecretAdmin && (user == null || !superAdminAuthority.isSuperAdmin(user))) {
     return channel -> channel.sendResponse(new BytesRestResponse(RestStatus.FORBIDDEN, ""));
 }
Suggestion importance[1-10]: 8

__

Why: This addresses a potential authentication bypass where valid superadmin secret authentication could be rejected if the user object is null. The logic should allow isSecretAdmin to proceed independently of the user check.

Medium
Fix incorrect admin flag in audit log

The failed login audit should pass true for the securityadmin parameter since this
is a superadmin authentication attempt. The current false value incorrectly
indicates this is not an admin-level authentication failure.

src/main/java/org/opensearch/security/auth/BackendRegistry.java [291-294]

 } else if (!gRPC && superAdminAuthority.hasSecretHeader(request)) {
     // Failed superadmin secret authentication attempt
-    auditLog.logFailedLogin("superadmin", false, null, request);
+    auditLog.logFailedLogin("superadmin", true, null, request);
 }
Suggestion importance[1-10]: 7

__

Why: The securityadmin parameter should be true for failed superadmin secret authentication attempts to accurately reflect the admin-level nature of the failure in audit logs. This is a correctness issue affecting audit trail accuracy.

Medium
Security
Mark insecure secret setting as deprecated

The insecure setting should log a warning when used, as storing superadmin secrets
in plain configuration files poses a significant security risk. Consider adding
deprecation or security warnings to guide users toward the secure variant.

src/main/java/org/opensearch/security/support/SecuritySettings.java [63-70]

 public static final Setting<SecureString> SECURITY_SUPERADMIN_SECRET_INSECURE_SETTING = SecureSetting.insecureString(
     ConfigConstants.SECURITY_SUPERADMIN_SECRET
 );
 
 public static final Setting<SecureString> SECURITY_SUPERADMIN_SECRET_SETTING = SecureSetting.secureString(
     ConfigConstants.SECURITY_SUPERADMIN_SECRET_SECURE,
-    SECURITY_SUPERADMIN_SECRET_INSECURE_SETTING
+    SECURITY_SUPERADMIN_SECRET_INSECURE_SETTING,
+    Setting.Property.Deprecated
 );
Suggestion importance[1-10]: 6

__

Why: Marking the insecure setting as deprecated would guide users toward the secure variant, improving security posture. However, the suggested code incorrectly applies Property.Deprecated to the secure setting instead of the insecure one.

Low
Add warning for misconfigured secret

Use constant-time comparison to prevent timing attacks. The current
MessageDigest.isEqual is appropriate, but the method should validate that
superadminSecret is not empty at initialization time rather than during each
authentication attempt to prevent information leakage through timing differences.

src/main/java/org/opensearch/security/configuration/SuperAdminAuthority.java [74-88]

 private boolean isSuperadminSecretValid(final String providedSecret) {
-    if (ObjectUtils.isEmpty(superadminSecret) || ObjectUtils.isEmpty(providedSecret)) {
+    if (ObjectUtils.isEmpty(providedSecret)) {
+        return false;
+    }
+    
+    if (ObjectUtils.isEmpty(superadminSecret)) {
+        log.warn("Superadmin secret not configured but authentication attempted");
         return false;
     }
 
     try {
         return MessageDigest.isEqual(
             superadminSecret.getBytes(StandardCharsets.UTF_8),
             providedSecret.getBytes(StandardCharsets.UTF_8)
         );
     } catch (Exception e) {
         log.debug("Error comparing superadmin secret", e);
         return false;
     }
 }
Suggestion importance[1-10]: 5

__

Why: Adding a warning when superadminSecret is empty improves operational visibility, but the security benefit is limited since the method already returns false. The timing attack concern is already addressed by MessageDigest.isEqual.

Low

Signed-off-by: Vishnutheep B <vishnutheep@gmail.com>
@itsmevichu itsmevichu marked this pull request as ready for review May 10, 2026 07:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants