Ctrl K
ComunidadGuías…

contrib/mysql-iam-plugin/aws_auth_plugin.cc

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
/*
 * MiniStack's Aurora MySQL AWSAuthenticationPlugin.
 *
 * Like AWS's plugin it asks the client for mysql_clear_password and reads the
 * RDS IAM token as the password. MiniStack decides: the plugin forwards the
 * user and token to its broker and accepts only an explicit allow. A missing
 * config, a timeout or any malformed answer denies.
 */

#include <netdb.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>

#include <string>

// MYSQL_ABI_CHECK omits MySQL's internal compiler headers. Dynamic plugin
// declarations still need the public-symbol visibility wrapper they provide.
#ifndef MY_ATTRIBUTE
#define MY_ATTRIBUTE(attributes) __attribute__(attributes)
#endif

#include <mysql/plugin_auth.h>

// Written by MiniStack when the container is ready: "<host> <port> <capability>".
static const char CONFIG_PATH[] = "/etc/ministack/rds-iam.conf";

static bool json_string(std::string &out, const std::string &value) {
  out += '"';
  for (unsigned char c : value) {
    if (c < 0x20 || c == 0x7f) return false;
    if (c == '"' || c == '\\') out += '\\';
    out += static_cast<char>(c);
  }
  out += '"';
  return true;
}

static bool broker_allows(const std::string &user, const std::string &token) {
  char host[256], port[6], capability[65];
  FILE *config = fopen(CONFIG_PATH, "r");
  if (!config) return false;
  int fields = fscanf(config, "%255s %5s %64s", host, port, capability);
  fclose(config);
  if (fields != 3 || strlen(capability) != 64) return false;

  std::string body = "{\"username\":";
  if (!json_string(body, user)) return false;
  body += ",\"token\":";
  if (!json_string(body, token)) return false;
  body += '}';
  std::string request = "POST /_ministack/rds/iam-auth HTTP/1.1\r\nHost: ";
  request += host;
  request += "\r\nContent-Type: application/json\r\nX-Ministack-RDS-Capability: ";
  request += capability;
  request += "\r\nContent-Length: " + std::to_string(body.size());
  request += "\r\nConnection: close\r\n\r\n" + body;

  struct addrinfo hints = {}, *addresses = nullptr;
  hints.ai_socktype = SOCK_STREAM;
  if (getaddrinfo(host, port, &hints, &addresses) != 0) return false;
  int fd = -1;
  struct timeval timeout = {3, 0};
  for (struct addrinfo *a = addresses; a && fd < 0; a = a->ai_next) {
    fd = socket(a->ai_family, a->ai_socktype, a->ai_protocol);
    if (fd < 0) continue;
    setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof timeout);
    setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof timeout);
    if (connect(fd, a->ai_addr, a->ai_addrlen) != 0) {
      close(fd);
      fd = -1;
    }
  }
  freeaddrinfo(addresses);
  if (fd < 0) return false;

  bool sent = true;
  for (size_t offset = 0; sent && offset < request.size();) {
    ssize_t n = send(fd, request.data() + offset, request.size() - offset, MSG_NOSIGNAL);
    sent = n > 0;
    if (sent) offset += n;
  }
  std::string response;
  char buffer[1024];
  ssize_t n;
  while (sent && response.size() < 8192 && (n = recv(fd, buffer, sizeof buffer, 0)) > 0)
    response.append(buffer, n);
  close(fd);
  return sent && response.compare(0, 13, "HTTP/1.1 200 ") == 0 &&
         response.find("{\"allowed\":true}") != std::string::npos;
}

static int authenticate(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info) {
  unsigned char *packet = nullptr;
  int size = vio->read_packet(vio, &packet);
  if (size < 0 || !packet) return CR_ERROR;
  info->password_used = PASSWORD_USED_YES;
  std::string token(reinterpret_cast<char *>(packet), size);
  if (!token.empty() && token.back() == '\0') token.pop_back();
  std::string user(info->user_name, info->user_name_length);
  // Anonymous accounts would authorize a name other than the account's.
  if (token.empty() || user.empty() || user != info->authenticated_as)
    return CR_ERROR;
  return broker_allows(user, token) ? CR_OK : CR_ERROR;
}

static int generate_authentication_string(char *outbuf,
                                          unsigned int *outbuflen,
                                          const char *inbuf,
                                          unsigned int inbuflen) {
  (void)outbuf;
  (void)inbuf;
  (void)inbuflen;
  *outbuflen = 0;
  return 0;
}

static int validate_authentication_string(char *const inbuf,
                                          unsigned int buflen) {
  (void)inbuf;
  (void)buflen;
  return 0;
}

static int set_salt(const char *password, unsigned int password_len,
                    unsigned char *salt, unsigned char *salt_len) {
  (void)password;
  (void)password_len;
  (void)salt;
  *salt_len = 0;
  return 0;
}

static struct st_mysql_auth aws_auth_handler = {
    MYSQL_AUTHENTICATION_INTERFACE_VERSION,
    "mysql_clear_password",
    authenticate,
    generate_authentication_string,
    validate_authentication_string,
    set_salt,
    AUTH_FLAG_PRIVILEGED_USER_FOR_PASSWORD_CHANGE,
    NULL,
};

mysql_declare_plugin(aws_auth_plugin) {
  MYSQL_AUTHENTICATION_PLUGIN,
  &aws_auth_handler,
  "AWSAuthenticationPlugin",
  "MiniStack",
  "Aurora IAM authentication compatibility plugin",
  PLUGIN_LICENSE_GPL,
  NULL,
  NULL,
  NULL,
  0x0100,
  NULL,
  NULL,
  NULL,
  0,
} mysql_declare_plugin_end;