summaryrefslogtreecommitdiffstats
path: root/source4/dsdb/samdb/ldb_modules/audit_util.c
diff options
context:
space:
mode:
Diffstat (limited to 'source4/dsdb/samdb/ldb_modules/audit_util.c')
-rw-r--r--source4/dsdb/samdb/ldb_modules/audit_util.c697
1 files changed, 697 insertions, 0 deletions
diff --git a/source4/dsdb/samdb/ldb_modules/audit_util.c b/source4/dsdb/samdb/ldb_modules/audit_util.c
new file mode 100644
index 0000000..9b8b06b
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/audit_util.c
@@ -0,0 +1,697 @@
+/*
+ ldb database module utility library
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * Common utility functions for SamDb audit logging.
+ *
+ */
+
+#include "includes.h"
+#include "ldb_module.h"
+#include "lib/audit_logging/audit_logging.h"
+
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+#include "libcli/security/dom_sid.h"
+#include "libcli/security/security_token.h"
+#include "auth/common_auth.h"
+#include "param/param.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+#include "dsdb/samdb/ldb_modules/audit_util_proto.h"
+
+#define MAX_LENGTH 1024
+
+#define min(a, b) (((a)>(b))?(b):(a))
+
+/*
+ * List of attributes considered secret or confidential the values of these
+ * attributes should not be displayed in log messages.
+ */
+static const char * const secret_attributes[] = {
+ DSDB_SECRET_ATTRIBUTES,
+ NULL};
+/*
+ * List of attributes that contain a password, used to detect password changes
+ */
+static const char * const password_attributes[] = {
+ DSDB_PASSWORD_ATTRIBUTES,
+ NULL};
+
+/*
+ * @brief Should the value of the specified value be redacted.
+ *
+ * The values of secret or password attributes should not be displayed.
+ *
+ * @param name The attributes name.
+ *
+ * @return True if the attribute should be redacted
+ */
+bool dsdb_audit_redact_attribute(const char * name)
+{
+
+ if (ldb_attr_in_list(secret_attributes, name)) {
+ return true;
+ }
+
+ if (ldb_attr_in_list(password_attributes, name)) {
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * @brief is the attribute a password attribute?
+ *
+ * Is the attribute a password attribute.
+ *
+ * @return True if the attribute is a "Password" attribute.
+ */
+bool dsdb_audit_is_password_attribute(const char * name)
+{
+
+ bool is_password = ldb_attr_in_list(password_attributes, name);
+ return is_password;
+}
+
+/*
+ * @brief Get the remote address from the ldb context.
+ *
+ * The remote address is stored in the ldb opaque value "remoteAddress"
+ * it is the responsibility of the higher level code to ensure that this
+ * value is set.
+ *
+ * @param ldb the ldb_context.
+ *
+ * @return the remote address if known, otherwise NULL.
+ */
+const struct tsocket_address *dsdb_audit_get_remote_address(
+ struct ldb_context *ldb)
+{
+ void *opaque_remote_address = NULL;
+ struct tsocket_address *remote_address;
+
+ opaque_remote_address = ldb_get_opaque(ldb,
+ "remoteAddress");
+ if (opaque_remote_address == NULL) {
+ return NULL;
+ }
+
+ remote_address = talloc_get_type(opaque_remote_address,
+ struct tsocket_address);
+ return remote_address;
+}
+
+/*
+ * @brief Get the actual user SID from ldb context.
+ *
+ * The actual user SID is stored in the ldb opaque value "networkSessionInfo"
+ * it is the responsibility of the higher level code to ensure that this
+ * value is set.
+ *
+ * @param ldb the ldb_context.
+ *
+ * @return the users actual sid.
+ */
+const struct dom_sid *dsdb_audit_get_actual_sid(struct ldb_context *ldb)
+{
+ void *opaque_session = NULL;
+ struct auth_session_info *session = NULL;
+ struct security_token *user_token = NULL;
+
+ opaque_session = ldb_get_opaque(ldb, DSDB_NETWORK_SESSION_INFO);
+ if (opaque_session == NULL) {
+ return NULL;
+ }
+
+ session = talloc_get_type(opaque_session, struct auth_session_info);
+ if (session == NULL) {
+ return NULL;
+ }
+
+ user_token = session->security_token;
+ if (user_token == NULL) {
+ return NULL;
+ }
+ return &user_token->sids[PRIMARY_USER_SID_INDEX];
+}
+/*
+ * @brief get the ldb error string.
+ *
+ * Get the ldb error string if set, otherwise get the generic error code
+ * for the status code.
+ *
+ * @param ldb the ldb_context.
+ * @param status the ldb_status code.
+ *
+ * @return a string describing the error.
+ */
+const char *dsdb_audit_get_ldb_error_string(
+ struct ldb_module *module,
+ int status)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ const char *err_string = ldb_errstring(ldb);
+
+ if (err_string == NULL) {
+ return ldb_strerror(status);
+ }
+ return err_string;
+}
+
+/*
+ * @brief get the SID of the user performing the operation.
+ *
+ * Get the SID of the user performing the operation.
+ *
+ * @param module the ldb_module.
+ *
+ * @return the SID of the currently logged on user.
+ */
+const struct dom_sid *dsdb_audit_get_user_sid(const struct ldb_module *module)
+{
+ struct security_token *user_token = NULL;
+
+ /*
+ * acl_user_token does not alter module so it's safe
+ * to discard the const.
+ */
+ user_token = acl_user_token(discard_const(module));
+ if (user_token == NULL) {
+ return NULL;
+ }
+ return &user_token->sids[PRIMARY_USER_SID_INDEX];
+
+}
+
+/*
+ * @brief is operation being performed using the system session.
+ *
+ * Is the operation being performed using the system session.
+ *
+ * @param module the ldb_module.
+ *
+ * @return true if the operation is being performed using the system session.
+ */
+bool dsdb_audit_is_system_session(const struct ldb_module *module)
+{
+ struct security_token *user_token = NULL;
+
+ /*
+ * acl_user_token does not alter module and security_token_is_system
+ * does not alter the security token so it's safe to discard the const.
+ */
+ user_token = acl_user_token(discard_const(module));
+ if (user_token == NULL) {
+ return false;
+ }
+ return security_token_is_system(user_token);;
+
+}
+
+/*
+ * @brief get the session identifier GUID
+ *
+ * Get the GUID that uniquely identifies the current authenticated session.
+ *
+ * @param module the ldb_module.
+ *
+ * @return the unique session GUID
+ */
+const struct GUID *dsdb_audit_get_unique_session_token(
+ const struct ldb_module *module)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(discard_const(module));
+ struct auth_session_info *session_info
+ = (struct auth_session_info *)ldb_get_opaque(
+ ldb,
+ DSDB_SESSION_INFO);
+ if(!session_info) {
+ return NULL;
+ }
+ return &session_info->unique_session_token;
+}
+
+/*
+ * @brief get the actual user session identifier
+ *
+ * Get the GUID that uniquely identifies the current authenticated session.
+ * This is the session of the connected user, as it may differ from the
+ * session the operation is being performed as, i.e. for operations performed
+ * under the system session.
+ *
+ * @param context the ldb_context.
+ *
+ * @return the unique session GUID
+ */
+const struct GUID *dsdb_audit_get_actual_unique_session_token(
+ struct ldb_context *ldb)
+{
+ struct auth_session_info *session_info
+ = (struct auth_session_info *)ldb_get_opaque(
+ ldb,
+ DSDB_NETWORK_SESSION_INFO);
+ if(!session_info) {
+ return NULL;
+ }
+ return &session_info->unique_session_token;
+}
+
+/*
+ * @brief Get a printable string value for the remote host address.
+ *
+ * Get a printable string representation of the remote host, for display in the
+ * the audit logs.
+ *
+ * @param ldb the ldb context.
+ * @param mem_ctx the talloc memory context that will own the returned string.
+ *
+ * @return A string representation of the remote host address or "Unknown"
+ *
+ */
+char *dsdb_audit_get_remote_host(struct ldb_context *ldb, TALLOC_CTX *mem_ctx)
+{
+ const struct tsocket_address *remote_address;
+ char* remote_host = NULL;
+
+ remote_address = dsdb_audit_get_remote_address(ldb);
+ if (remote_address == NULL) {
+ remote_host = talloc_asprintf(mem_ctx, "Unknown");
+ return remote_host;
+ }
+
+ remote_host = tsocket_address_string(remote_address, mem_ctx);
+ return remote_host;
+}
+
+/*
+ * @brief get a printable representation of the primary DN.
+ *
+ * Get a printable representation of the primary DN. The primary DN is the
+ * DN of the object being added, deleted, modified or renamed.
+ *
+ * @param the ldb_request.
+ *
+ * @return a printable and linearized DN
+ */
+const char* dsdb_audit_get_primary_dn(const struct ldb_request *request)
+{
+ struct ldb_dn *dn = NULL;
+ switch (request->operation) {
+ case LDB_ADD:
+ if (request->op.add.message != NULL) {
+ dn = request->op.add.message->dn;
+ }
+ break;
+ case LDB_MODIFY:
+ if (request->op.mod.message != NULL) {
+ dn = request->op.mod.message->dn;
+ }
+ break;
+ case LDB_DELETE:
+ dn = request->op.del.dn;
+ break;
+ case LDB_RENAME:
+ dn = request->op.rename.olddn;
+ break;
+ default:
+ dn = NULL;
+ break;
+ }
+ if (dn == NULL) {
+ return NULL;
+ }
+ return ldb_dn_get_linearized(dn);
+}
+
+/*
+ * @brief Get the ldb_message from a request.
+ *
+ * Get the ldb_message for the request, returns NULL is there is no
+ * associated ldb_message
+ *
+ * @param The request
+ *
+ * @return the message associated with this request, or NULL
+ */
+const struct ldb_message *dsdb_audit_get_message(
+ const struct ldb_request *request)
+{
+ switch (request->operation) {
+ case LDB_ADD:
+ return request->op.add.message;
+ case LDB_MODIFY:
+ return request->op.mod.message;
+ default:
+ return NULL;
+ }
+}
+
+/*
+ * @brief get the secondary dn, i.e. the target dn for a rename.
+ *
+ * Get the secondary dn, i.e. the target for a rename. This is only applicable
+ * got a rename operation, for the non rename operations this function returns
+ * NULL.
+ *
+ * @param request the ldb_request.
+ *
+ * @return the secondary dn in a printable and linearized form.
+ */
+const char *dsdb_audit_get_secondary_dn(const struct ldb_request *request)
+{
+ switch (request->operation) {
+ case LDB_RENAME:
+ return ldb_dn_get_linearized(request->op.rename.newdn);
+ default:
+ return NULL;
+ }
+}
+
+/*
+ * @brief Map the request operation to a description.
+ *
+ * Get a description of the operation for logging
+ *
+ * @param request the ldb_request
+ *
+ * @return a string describing the operation, or "Unknown" if the operation
+ * is not known.
+ */
+const char *dsdb_audit_get_operation_name(const struct ldb_request *request)
+{
+ switch (request->operation) {
+ case LDB_SEARCH:
+ return "Search";
+ case LDB_ADD:
+ return "Add";
+ case LDB_MODIFY:
+ return "Modify";
+ case LDB_DELETE:
+ return "Delete";
+ case LDB_RENAME:
+ return "Rename";
+ case LDB_EXTENDED:
+ return "Extended";
+ case LDB_REQ_REGISTER_CONTROL:
+ return "Register Control";
+ case LDB_REQ_REGISTER_PARTITION:
+ return "Register Partition";
+ default:
+ return "Unknown";
+ }
+}
+
+/*
+ * @brief get a description of a modify action for logging.
+ *
+ * Get a brief description of the modification action suitable for logging.
+ *
+ * @param flags the ldb_attributes flags.
+ *
+ * @return a brief description, or "unknown".
+ */
+const char *dsdb_audit_get_modification_action(unsigned int flags)
+{
+ switch (LDB_FLAG_MOD_TYPE(flags)) {
+ case LDB_FLAG_MOD_ADD:
+ return "add";
+ case LDB_FLAG_MOD_DELETE:
+ return "delete";
+ case LDB_FLAG_MOD_REPLACE:
+ return "replace";
+ default:
+ return "unknown";
+ }
+}
+
+/*
+ * @brief Add an ldb_value to a json object array
+ *
+ * Convert the current ldb_value to a JSON object and append it to array.
+ * {
+ * "value":"xxxxxxxx",
+ * "base64":true
+ * "truncated":true
+ * }
+ *
+ * value is the JSON string representation of the ldb_val,
+ * will be null if the value is zero length. The value will be
+ * truncated if it is more than MAX_LENGTH bytes long. It will also
+ * be base64 encoded if it contains any non printable characters.
+ *
+ * base64 Indicates that the value is base64 encoded, will be absent if the
+ * value is not encoded.
+ *
+ * truncated Indicates that the length of the value exceeded MAX_LENGTH and was
+ * truncated. Note that values are truncated and then base64 encoded.
+ * so an encoded value can be longer than MAX_LENGTH.
+ *
+ * @param array the JSON array to append the value to.
+ * @param lv the ldb_val to convert and append to the array.
+ *
+ */
+static int dsdb_audit_add_ldb_value(struct json_object *array,
+ const struct ldb_val lv)
+{
+ bool base64;
+ int len;
+ struct json_object value = json_empty_object;
+ int rc = 0;
+
+ json_assert_is_array(array);
+ if (json_is_invalid(array)) {
+ return -1;
+ }
+
+ if (lv.length == 0 || lv.data == NULL) {
+ rc = json_add_object(array, NULL, NULL);
+ if (rc != 0) {
+ goto failure;
+ }
+ return 0;
+ }
+
+ base64 = ldb_should_b64_encode(NULL, &lv);
+ len = min(lv.length, MAX_LENGTH);
+ value = json_new_object();
+ if (json_is_invalid(&value)) {
+ goto failure;
+ }
+
+ if (lv.length > MAX_LENGTH) {
+ rc = json_add_bool(&value, "truncated", true);
+ if (rc != 0) {
+ goto failure;
+ }
+ }
+ if (base64) {
+ TALLOC_CTX *ctx = talloc_new(NULL);
+ char *encoded = ldb_base64_encode(
+ ctx,
+ (char*) lv.data,
+ len);
+
+ if (ctx == NULL) {
+ goto failure;
+ }
+
+ rc = json_add_bool(&value, "base64", true);
+ if (rc != 0) {
+ TALLOC_FREE(ctx);
+ goto failure;
+ }
+ rc = json_add_string(&value, "value", encoded);
+ if (rc != 0) {
+ TALLOC_FREE(ctx);
+ goto failure;
+ }
+ TALLOC_FREE(ctx);
+ } else {
+ rc = json_add_stringn(&value, "value", (char *)lv.data, len);
+ if (rc != 0) {
+ goto failure;
+ }
+ }
+ /*
+ * As array is a JSON array the element name is NULL
+ */
+ rc = json_add_object(array, NULL, &value);
+ if (rc != 0) {
+ goto failure;
+ }
+ return 0;
+failure:
+ /*
+ * In the event of a failure value will not have been added to array
+ * so it needs to be freed to prevent a leak.
+ */
+ json_free(&value);
+ DBG_ERR("unable to add ldb value to JSON audit message\n");
+ return -1;
+}
+
+/*
+ * @brief Build a JSON object containing the attributes in an ldb_message.
+ *
+ * Build a JSON object containing all the attributes in an ldb_message.
+ * The attributes are keyed by attribute name, the values of "secret attributes"
+ * are suppressed.
+ *
+ * {
+ * "password":{
+ * "redacted":true,
+ * "action":"delete"
+ * },
+ * "name":{
+ * "values": [
+ * {
+ * "value":"xxxxxxxx",
+ * "base64":true
+ * "truncated":true
+ * },
+ * ],
+ * "action":"add",
+ * }
+ * }
+ *
+ * values is an array of json objects generated by add_ldb_value.
+ * redacted indicates that the attribute is secret.
+ * action is only set for modification operations.
+ *
+ * @param operation the ldb operation being performed
+ * @param message the ldb_message to process.
+ *
+ * @return A populated json object.
+ *
+ */
+struct json_object dsdb_audit_attributes_json(
+ enum ldb_request_type operation,
+ const struct ldb_message* message)
+{
+
+ unsigned int i, j;
+ struct json_object attributes = json_new_object();
+
+ if (json_is_invalid(&attributes)) {
+ goto failure;
+ }
+ for (i=0;i<message->num_elements;i++) {
+ struct json_object actions = json_empty_object;
+ struct json_object attribute = json_empty_object;
+ struct json_object action = json_empty_object;
+ const char *name = message->elements[i].name;
+ int rc = 0;
+
+ action = json_new_object();
+ if (json_is_invalid(&action)) {
+ goto failure;
+ }
+
+ /*
+ * If this is a modify operation tag the attribute with
+ * the modification action.
+ */
+ if (operation == LDB_MODIFY) {
+ const char *act = NULL;
+ const int flags = message->elements[i].flags;
+ act = dsdb_audit_get_modification_action(flags);
+ rc = json_add_string(&action, "action", act);
+ if (rc != 0) {
+ json_free(&action);
+ goto failure;
+ }
+ }
+ if (operation == LDB_ADD) {
+ rc = json_add_string(&action, "action", "add");
+ if (rc != 0) {
+ json_free(&action);
+ goto failure;
+ }
+ }
+
+ /*
+ * If the attribute is a secret attribute, tag it as redacted
+ * and don't include the values
+ */
+ if (dsdb_audit_redact_attribute(name)) {
+ rc = json_add_bool(&action, "redacted", true);
+ if (rc != 0) {
+ json_free(&action);
+ goto failure;
+ }
+ } else {
+ struct json_object values;
+ /*
+ * Add the values for the action
+ */
+ values = json_new_array();
+ if (json_is_invalid(&values)) {
+ json_free(&action);
+ goto failure;
+ }
+
+ for (j=0;j<message->elements[i].num_values;j++) {
+ rc = dsdb_audit_add_ldb_value(
+ &values, message->elements[i].values[j]);
+ if (rc != 0) {
+ json_free(&values);
+ json_free(&action);
+ goto failure;
+ }
+ }
+ rc = json_add_object(&action, "values", &values);
+ if (rc != 0) {
+ json_free(&values);
+ json_free(&action);
+ goto failure;
+ }
+ }
+ attribute = json_get_object(&attributes, name);
+ if (json_is_invalid(&attribute)) {
+ json_free(&action);
+ goto failure;
+ }
+ actions = json_get_array(&attribute, "actions");
+ if (json_is_invalid(&actions)) {
+ json_free(&action);
+ goto failure;
+ }
+ rc = json_add_object(&actions, NULL, &action);
+ if (rc != 0) {
+ json_free(&action);
+ goto failure;
+ }
+ rc = json_add_object(&attribute, "actions", &actions);
+ if (rc != 0) {
+ json_free(&actions);
+ goto failure;
+ }
+ rc = json_add_object(&attributes, name, &attribute);
+ if (rc != 0) {
+ json_free(&attribute);
+ goto failure;
+ }
+ }
+ return attributes;
+failure:
+ json_free(&attributes);
+ DBG_ERR("Unable to create ldb attributes JSON audit message\n");
+ return attributes;
+}