TokenMasker.java
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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.
package org.apache.doris.common.util;
import com.google.common.base.Strings;
import com.google.common.hash.Hashing;
import java.nio.charset.StandardCharsets;
/**
* Helpers to render a secret (bearer token, auth token, ...) in log lines and error messages
* without writing the secret itself, since logs are routinely shipped to places that are far
* less protected than the credential store.
*
* <p>Two different renderings are offered, pick by what the reader of the message needs:
* {@link #tokenId} when the reader only needs to correlate messages about the same secret, and
* {@link #maskPrefix} when a human needs to recognize <i>which</i> configured secret was used.
*/
public class TokenMasker {
public static final String EMPTY_TOKEN = "<empty>";
// A truncated digest: long enough that two live tokens are very unlikely to collide,
// short enough that it is useless as a credential.
private static final int TOKEN_ID_LEN = 8;
private static final String TOKEN_ID_PREFIX = "sha256:";
// Minimum token length required before we reveal a masked prefix. Shorter tokens would
// leak too large a fraction of the secret, so they are hidden entirely with only a length hint.
private static final int MIN_TOKEN_LEN_FOR_PREFIX = 8;
private static final int TOKEN_PREFIX_LEN = 3;
private TokenMasker() {
}
/**
* Returns a stable, non-reversible handle for a token, e.g. {@code sha256:1a2b3c4d}. The same
* token always renders to the same handle, so log lines and the error message handed back to
* the client can be matched up, while no part of the secret is disclosed.
*/
public static String tokenId(String token) {
if (Strings.isNullOrEmpty(token)) {
return EMPTY_TOKEN;
}
return TOKEN_ID_PREFIX + Hashing.sha256().hashString(token, StandardCharsets.UTF_8).toString()
.substring(0, TOKEN_ID_LEN);
}
/**
* Masks a token by revealing only a short leading prefix (e.g. {@code abc***}), so that a
* token mismatch is diagnosable during rotation, while never logging the full secret. Empty
* tokens and tokens too short to safely show a prefix are hidden. Prefer {@link #tokenId}
* unless the reader really needs to recognize the secret by sight.
*/
public static String maskPrefix(String token) {
if (Strings.isNullOrEmpty(token)) {
return EMPTY_TOKEN;
}
if (token.length() < MIN_TOKEN_LEN_FOR_PREFIX) {
// Too short to reveal any prefix without leaking a large fraction of the secret.
return "<hidden, token length " + token.length() + " < " + MIN_TOKEN_LEN_FOR_PREFIX + ">";
}
return token.substring(0, TOKEN_PREFIX_LEN) + "***";
}
}