All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.vertx.ext.auth.audit.impl.SecurityAuditLogger Maven / Gradle / Ivy

The newest version!
package io.vertx.ext.auth.audit.impl;

import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpVersion;
import io.vertx.core.internal.logging.Logger;
import io.vertx.core.internal.logging.LoggerFactory;
import io.vertx.core.json.JsonObject;
import io.vertx.core.net.SocketAddress;
import io.vertx.ext.auth.User;
import io.vertx.ext.auth.audit.Marker;
import io.vertx.ext.auth.audit.SecurityAudit;
import io.vertx.ext.auth.authentication.Credentials;
import io.vertx.ext.auth.authorization.Authorization;

import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public final class SecurityAuditLogger implements SecurityAudit {

  public static final Logger LOGGER = LoggerFactory.getLogger(SecurityAudit.class);

  private static final String FORMAT = System.getProperty("io.vertx.ext.auth.audit.format", "rfc5424");

  private static final String MASK = "********************************";

  private static String mask(String input) {
    int len = input.length();
    if (len > 32) {
      return MASK + "...";
    }
    return MASK.substring(0, len);
  }

  private static final Set MASKED = new HashSet<>();

  static {
    Collections.addAll(MASKED, "password", "secret", "jwt", "nonce", "cnonce", "assertion", "token", "challenge");
  }

  /**
   * Escape any NLF (newline function) and Backspace to prevent log injection attacks.
   *
   * @param value string to convert
   * @return converted string
   * @see Unicode Standard
   */
  private static void escapeNLFChars(StringBuilder sb, String value) {
    for (char c : value.toCharArray()) {
      switch (c) {
        case '\n':
          sb.append("\\n");
          break;
        case '\r':
          sb.append("\\r");
          break;
        case '\u0085':
          // NEL
          sb.append("\\u0085");
          break;
        case '\u000B':
          // VT
          sb.append("\\u000B");
          break;
        case '\u000C':
          // FF
          sb.append("\\u000C");
          break;
        case '\u2028':
          // LS
          sb.append("\\u2028");
          break;
        case '\u2029':
          // PS
          sb.append("\\u2029");
          break;
        case '\b':
          sb.append("\\b");
          break;
        default:
          sb.append(c);
          break;
      }
    }
  }


  private SocketAddress source;
  private SocketAddress destination;
  private String resource;
  private Credentials credentials;
  private User user;
  private Authorization authorization;
  private int status;

  public static boolean isEnabled() {
    return LOGGER.isInfoEnabled();
  }

  @Override
  public SecurityAudit source(SocketAddress address) {
    this.source = address;
    return this;
  }

  @Override
  public SecurityAudit destination(SocketAddress address) {
    this.destination = address;
    return this;
  }

  @Override
  public SecurityAudit resource(HttpVersion version, HttpMethod method, String path) {
    StringBuilder sb = new StringBuilder();
    switch (version){
      case HTTP_1_0:
        sb.append("HTTP/1.0");
        break;
      case HTTP_1_1:
        sb.append("HTTP/1.1");
        break;
      case HTTP_2:
        sb.append("HTTP/2.0");
        break;
    }

    sb.append(' ');
    sb.append(method.name());
    sb.append(' ');
    sb.append(path);

    resource = sb.toString();
    return this;
  }

  @Override
  public SecurityAudit resource(String resource) {
    this.resource = resource;
    return this;
  }

  @Override
  public SecurityAudit credentials(Credentials credentials) {
    this.credentials = credentials;
    return this;
  }

  @Override
  public SecurityAudit user(User user) {
    this.user = user;
    return this;
  }

  @Override
  public SecurityAudit authorization(Authorization authorization) {
    this.authorization = authorization;
    return this;
  }

  @Override
  public SecurityAudit status(int status) {
    this.status = status;
    return this;
  }

  @Override
  public void audit(Marker marker, boolean success) {
    switch (FORMAT) {
      case "json":
        auditJSON(marker, success);
        break;
      case "rfc5424":
      default:
        auditRFC5424(marker, success);
        break;
    }
  }

  private void auditRFC5424(Marker marker, boolean success) {
    StringBuilder sb = new StringBuilder();

    sb
      .append('[')
      .append(marker.name());

    sb.append(" epoch=\"")
      .append(System.currentTimeMillis())
      .append('"');

    if (source != null) {
      if (source.isInetSocket()) {
        sb.append(" source=\"")
          .append(source.host());
      } else {
        sb.append(" source=\"");
        escapeNLFChars(sb, source.path());
      }
      sb.append('"');
    }
    if (destination != null) {
      if (destination.isInetSocket()) {
        sb.append(" destination=\"")
          .append(destination.host());
      } else {
        sb.append(" destination=\"");
        escapeNLFChars(sb, destination.path());
      }
      sb.append('"');
    }
    if (resource != null) {
      sb.append(" resource=\"");
      escapeNLFChars(sb, resource);
      sb.append('"');
    }

    switch (marker) {
      case AUTHENTICATION:
        if (credentials != null) {
          for (Map.Entry kv : credentials.toJson()) {
            String key = kv.getKey();
            sb.append(' ');
            escapeNLFChars(sb, key);
            sb.append('=');
            Object value = kv.getValue();
            if (value == null) {
              sb.append("null");
            } else {
              sb.append('"');
              if (MASKED.contains(key)) {
                sb.append(mask(value.toString()));
              } else {
                escapeNLFChars(sb, value.toString());
              }
              sb.append('"');
            }
          }
        }

        break;
      case AUTHORIZATION:
        if (user != null) {
          String sub = user.subject();
          if (sub != null) {
            sb.append(" subject=\"");
            escapeNLFChars(sb, sub);
            sb.append('"');
          }
        }
        if (authorization != null) {
          sb.append(" authorization=\"");
          escapeNLFChars(sb, authorization.toString());
          sb.append('"');
        }
        break;
      case REQUEST:
        sb.append(" status=").append(status);
        break;
    }
    sb
      .append("] ")
      .append(success ? "OK" : "FAIL");

    LOGGER.info(sb.toString());
  }

  private void auditJSON(Marker marker, boolean success) {
    final JsonObject json = new JsonObject();
    json.put("marker", marker.name());

    json.put("epoch", System.currentTimeMillis());

    if (source != null) {
      if (source.isInetSocket()) {
        json.put("source", source.host());
      } else {
        json.put("source", source.path());
      }
    }
    if (destination != null) {
      if (destination.isInetSocket()) {
        json.put("destination", destination.host());
      } else {
        json.put("destination", destination.path());
      }
    }
    if (resource != null) {
      json.put("resource", resource);
    }

    switch (marker) {
      case AUTHENTICATION:
        if (credentials != null) {
          for (Map.Entry kv : credentials.toJson()) {
            String key = kv.getKey();
            Object value = kv.getValue();
            if (value != null) {
              if (MASKED.contains(key)) {
                json.put(key, mask(value.toString()));
              } else {
                json.put(key, value.toString());
              }
            }
          }
        }
        break;
      case AUTHORIZATION:
        if (user != null) {
          String sub = user.subject();
          if (sub != null) {
            json.put("subject", sub);
          }
        }
        if (authorization != null) {
          json.put("authorization", authorization.toString());
        }
        break;
      case REQUEST:
        json.put("status", status);
        break;
    }

    json.put("result", success ? "OK" : "FAIL");

    LOGGER.info(json.encode());
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy