diff options
author | Gary Lockyer <gary@catalyst.net.nz> | 2017-03-06 16:16:51 +1300 |
---|---|---|
committer | Andrew Bartlett <abartlet@samba.org> | 2017-03-29 02:37:27 +0200 |
commit | 387eb18a1ccdcea3040476efbc2769de40ccf86e (patch) | |
tree | 8ece2624609a73b544bf8189ba0c3bcc3ffc10e8 | |
parent | 366f8cf0903e3583fda42696df62a5337f22131f (diff) | |
download | samba-387eb18a1ccdcea3040476efbc2769de40ccf86e.tar.gz |
auth_log: Add JSON logging of Authorisation and Authentications
Signed-off-by: Gary Lockyer <gary@catalyst.net.nz>
Pair-Programmed: Andrew Bartlett <abartlet@samba.org>
-rw-r--r-- | auth/auth_log.c | 597 | ||||
-rw-r--r-- | auth/wscript_build | 2 | ||||
-rw-r--r-- | auth/wscript_configure | 7 | ||||
-rw-r--r-- | docs-xml/smbdotconf/logging/loglevel.xml | 23 | ||||
-rw-r--r-- | lib/util/debug.c | 1 | ||||
-rw-r--r-- | lib/util/debug.h | 2 | ||||
-rw-r--r-- | wscript | 1 |
7 files changed, 584 insertions, 49 deletions
diff --git a/auth/auth_log.c b/auth/auth_log.c index b7b8810f03d..9ff2491dee3 100644 --- a/auth/auth_log.c +++ b/auth/auth_log.c @@ -30,6 +30,21 @@ #define AUTH_ANONYMOUS_LEVEL 5 #define AUTHZ_ANONYMOUS_LEVEL 5 +#define AUTHZ_JSON_TYPE "Authorization" +#define AUTH_JSON_TYPE "Authentication" + +/* + * JSON message version numbers + * + * If adding a field increment the minor version + * If removing or changing the format/meaning of a field + * increment the major version. + */ +#define AUTH_MAJOR 1 +#define AUTH_MINOR 0 +#define AUTHZ_MAJOR 1 +#define AUTHZ_MINOR 0 + #include "includes.h" #include "../lib/tsocket/tsocket.h" #include "common_auth.h" @@ -84,6 +99,426 @@ static const char* get_timestamp( TALLOC_CTX *frame ) * authorisation attempt. * */ +static const char* get_password_type(const struct auth_usersupplied_info *ui); + +#ifdef HAVE_JANSSON + +#include <jansson.h> +#include "system/time.h" + +/* + * Context required by the JSON generation + * routines + * + */ +struct json_context { + json_t *root; + bool error; +}; + +/* + * Write the json object to the debug lines. + * + */ +static void log_json( struct json_context *context, + const char *type, int debug_class, int debug_level) +{ + char* json = NULL; + + if( context->error) { + return; + } + + json = json_dumps( context->root, 0); + if (json == NULL) { + DBG_ERR( "Unable to convert JSON object to string\n"); + context->error = true; + return; + } + + DEBUGC( debug_class, debug_level, ( "JSON %s: %s\n", type, json)); + + if (json) { + free(json); + } + +} + +/* + * Create a new json logging context. + * + * Free with a call to free_json_context + * + */ +static struct json_context get_json_context( void) { + + struct json_context context; + context.error = false; + + context.root = json_object(); + if (context.root == NULL) { + context.error = true; + DBG_ERR("Unable to create json_object\n"); + } + return context; +} + +/* + * free a previously created json_context + * + */ +static void free_json_context(struct json_context *context) +{ + if (context->root) { + json_decref( context->root); + } +} + +/* + * Output a JSON pair with name name and integer value value + * + */ +static void add_int(struct json_context *context, + const char* name, + const int value) +{ + int rc = 0; + + if (context->error) { + return; + } + + rc = json_object_set_new( context->root, name, json_integer( value)); + if (rc) { + DBG_ERR("Unable to set name [%s] value [%d]\n", name, value); + context->error = true; + } + +} + +/* + * Output a JSON pair with name name and string value value + * + */ +static void add_string(struct json_context *context, + const char* name, + const char* value) +{ + int rc = 0; + + if (context->error) { + return; + } + + if (value) { + rc = json_object_set_new(context->root, name, json_string(value)); + } else { + rc = json_object_set_new(context->root, name, json_null()); + } + if (rc) { + DBG_ERR("Unable to set name [%s] value [%s]\n", name, value); + context->error = true; + } +} + + +/* + * Output a JSON pair with name name and object value + * + */ +static void add_object(struct json_context *context, + const char* name, + struct json_context *value) +{ + int rc = 0; + + if (value->error) { + context->error = true; + } + if (context->error) { + return; + } + rc = json_object_set_new(context->root, name, value->root); + if (rc) { + DBG_ERR("Unable to add object [%s]\n", name); + context->error = true; + } +} + +/* + * Output a version object + * + * "version":{"major":1,"minor":0} + * + */ +static void add_version( struct json_context *context, int major, int minor) +{ + struct json_context version = get_json_context(); + add_int(&version, "major", major); + add_int(&version, "minor", minor); + add_object(context, "version", &version); +} + +/* + * Output the current date and time as a timestamp in ISO 8601 format + * + * "timestamp":"2017-03-06T17:18:04.455081+1300" + * + */ +static void add_timestamp( struct json_context *context) +{ + char buffer[40]; /* formatted time less usec and timezone */ + char timestamp[50]; /* the formatted ISO 8601 time stamp */ + char tz[10]; /* formatted time zone */ + struct tm* tm_info; /* current local time */ + struct timeval tv; /* current system time */ + int r; /* response code from gettimeofday */ + + if (context->error) { + return; + } + + r = gettimeofday(&tv, NULL); + if (r) { + DBG_ERR("Unable to get time of day: (%d) %s\n", + errno, + strerror( errno)); + context->error = true; + return; + } + + tm_info = localtime(&tv.tv_sec); + if (tm_info == NULL) { + DBG_ERR("Unable to determine local time\n"); + context->error = true; + return; + } + + strftime(buffer, sizeof(buffer)-1, "%Y-%m-%dT%T", tm_info); + strftime(tz, sizeof(tz)-1, "%z", tm_info); + snprintf(timestamp, sizeof(timestamp),"%s.%06ld%s", + buffer, tv.tv_usec, tz); + add_string(context,"timestamp", timestamp); +} + + +/* + * Output an address pair, with name name. + * + * "localAddress":"ipv6::::0" + * + */ +static void add_address(struct json_context *context, + const char *name, + const struct tsocket_address *address) +{ + char *s = NULL; + TALLOC_CTX *frame = talloc_stackframe(); + + if (context->error) { + return; + } + + s = tsocket_address_string(address, frame); + add_string(context, name, s); + talloc_free(frame); + +} + +/* + * Output a SID with name name + * + * "sid":"S-1-5-18" + * + */ +static void add_sid(struct json_context *context, + const char *name, + const struct dom_sid *sid) +{ + char sid_buf[DOM_SID_STR_BUFLEN]; + + if (context->error) { + return; + } + + dom_sid_string_buf(sid, sid_buf, sizeof(sid_buf)); + add_string(context, name, sid_buf); +} + +/* + * Write a machine parsable json formatted authentication log entry. + * + * IF removing or changing the format/meaning of a field please update the + * major version number AUTH_MAJOR + * + * IF adding a new field please update the minor version number AUTH_MINOR + * + * To process the resulting log lines from the commend line use jq to + * parse the json. + * + * grep "JSON Authentication" log file | + * sed 's;^[^{]*;;' | + * jq -rc '"\(.timestamp)\t\(.Authentication.status)\t + * \(.Authentication.clientDomain)\t + * \(.Authentication.clientAccount) + * \t\(.Authentication.workstation) + * \t\(.Authentication.remoteAddress) + * \t\(.Authentication.localAddress)"' + */ +static void log_authentication_event_json( + const struct auth_usersupplied_info *ui, + NTSTATUS status, + const char *domain_name, + const char *account_name, + const char *unix_username, + struct dom_sid *sid, + int debug_level) +{ + struct json_context context = get_json_context(); + struct json_context authentication; + char negotiate_flags[11]; + + add_timestamp(&context); + add_string(&context, "type", AUTH_JSON_TYPE); + + authentication = get_json_context(); + add_version(&authentication, AUTH_MAJOR, AUTH_MINOR); + add_string(&authentication, "status", nt_errstr( status)); + add_address(&authentication, "localAddress", ui->local_host); + add_address(&authentication, "remoteAddress", ui->remote_host); + add_string(&authentication, + "serviceDescription", + ui->service_description); + add_string(&authentication, "authDescription", ui->auth_description); + add_string(&authentication, "clientDomain", ui->client.domain_name); + add_string(&authentication, "clientAccount", ui->client.account_name); + add_string(&authentication, "workstation", ui->workstation_name); + add_string(&authentication, "becameAccount", account_name); + add_string(&authentication, "becameDomain", domain_name); + add_sid(&authentication, "becameSid", sid); + add_string(&authentication, "mappedAccount", ui->mapped.account_name); + add_string(&authentication, "mappedDomain", ui->mapped.domain_name); + add_string(&authentication, + "netlogonComputer", + ui->netlogon_trust_account.computer_name); + add_string(&authentication, + "netlogonTrustAccount", + ui->netlogon_trust_account.account_name); + snprintf(negotiate_flags, + sizeof( negotiate_flags), + "0x%08X", + ui->netlogon_trust_account.negotiate_flags); + add_string(&authentication, "netlogonNegotiateFlags", negotiate_flags); + add_int(&authentication, + "netlogonSecureChannelType", + ui->netlogon_trust_account.secure_channel_type); + add_sid(&authentication, + "netlogonTrustAccountSid", + ui->netlogon_trust_account.sid); + add_string(&authentication, "passwordType", get_password_type( ui)); + add_object(&context,AUTH_JSON_TYPE, &authentication); + + log_json(&context, AUTH_JSON_TYPE, DBGC_AUTH_AUDIT, debug_level); + free_json_context(&context); +} + +/* + * Log details of a successful authorization to a service, + * in a machine parsable json format + * + * IF removing or changing the format/meaning of a field please update the + * major version number AUTHZ_MAJOR + * + * IF adding a new field please update the minor version number AUTHZ_MINOR + * + * To process the resulting log lines from the commend line use jq to + * parse the json. + * + * grep "JSON Authentication" log_file |\ + * sed "s;^[^{]*;;" |\ + * jq -rc '"\(.timestamp)\t + * \(.Authorization.domain)\t + * \(.Authorization.account)\t + * \(.Authorization.remoteAddress)"' + * + */ +static void log_successful_authz_event_json( + const struct tsocket_address *remote, + const struct tsocket_address *local, + const char *service_description, + const char *auth_type, + const char *transport_protection, + struct auth_session_info *session_info, + int debug_level) +{ + struct json_context context = get_json_context(); + struct json_context authorization; + char account_flags[11]; + + //start_object(&context, NULL); + add_timestamp(&context); + add_string(&context, "type", AUTHZ_JSON_TYPE); + authorization = get_json_context(); + add_version(&authorization, AUTHZ_MAJOR, AUTHZ_MINOR); + add_address(&authorization, "localAddress", local); + add_address(&authorization, "remoteAddress", remote); + add_string(&authorization, "serviceDescription", service_description); + add_string(&authorization, "authType", auth_type); + add_string(&authorization, "domain", session_info->info->domain_name); + add_string(&authorization, "account", session_info->info->account_name); + add_sid(&authorization, "sid", &session_info->security_token->sids[0]); + add_string(&authorization, + "logonServer", + session_info->info->logon_server); + add_string(&authorization, "transportProtection", transport_protection); + + snprintf(account_flags, + sizeof( account_flags), + "0x%08X", + session_info->info->acct_flags); + add_string(&authorization, "accountFlags", account_flags); + add_object(&context,AUTHZ_JSON_TYPE, &authorization); + + log_json(&context, + AUTHZ_JSON_TYPE, + DBGC_AUTH_AUDIT, + debug_level); + free_json_context(&context); +} + +#else + +static void log_authentication_event_json( + const struct auth_usersupplied_info *ui, + NTSTATUS status, + const char *domain_name, + const char *account_name, + const char *unix_username, + struct dom_sid *sid, + int debug_level) +{ + return; +} + +static void log_successful_authz_event_json( + const struct tsocket_address *remote, + const struct tsocket_address *local, + const char *service_description, + const char *auth_type, + const char *transport_protection, + struct auth_session_info *session_info, + int debug_level) +{ + return; +} + +#endif + +/* + * Determine the type of the password supplied for the + * authorisation attempt. + * + */ static const char* get_password_type(const struct auth_usersupplied_info *ui) { @@ -116,16 +551,17 @@ static const char* get_password_type(const struct auth_usersupplied_info *ui) } /* - * Log details of an authentication attempt. - * Successful and unsuccessful attempts are logged. + * Write a human readable authentication log entry. * */ -void log_authentication_event(const struct auth_usersupplied_info *ui, - NTSTATUS status, - const char *domain_name, - const char *account_name, - const char *unix_username, - struct dom_sid *sid) +static void log_authentication_event_human_readable( + const struct auth_usersupplied_info *ui, + NTSTATUS status, + const char *domain_name, + const char *account_name, + const char *unix_username, + struct dom_sid *sid, + int debug_level) { TALLOC_CTX *frame = NULL; @@ -138,20 +574,6 @@ void log_authentication_event(const struct auth_usersupplied_info *ui, char *logon_line = NULL; const char *password_type = NULL; - /* set the log level */ - int debug_level = AUTH_FAILURE_LEVEL; - - if (NT_STATUS_IS_OK(status)) { - debug_level = AUTH_SUCCESS_LEVEL; - if (dom_sid_equal(sid, &global_sid_Anonymous)) { - debug_level = AUTH_ANONYMOUS_LEVEL; - } - } - - if (!CHECK_DEBUGLVLC( DBGC_AUTH_AUDIT, debug_level)) { - return; - } - frame = talloc_stackframe(); password_type = get_password_type( ui); @@ -183,10 +605,11 @@ void log_authentication_event(const struct auth_usersupplied_info *ui, log_escape(frame, account_name), sid_buf); } else { - logon_line = talloc_asprintf(frame, - " mapped to [%s]\\[%s].", - log_escape(frame, ui->mapped.domain_name), - log_escape(frame, ui->mapped.account_name)); + logon_line = talloc_asprintf( + frame, + " mapped to [%s]\\[%s].", + log_escape(frame, ui->mapped.domain_name), + log_escape(frame, ui->mapped.account_name)); } DEBUGC( DBGC_AUTH_AUDIT, debug_level, ( @@ -212,23 +635,65 @@ void log_authentication_event(const struct auth_usersupplied_info *ui, talloc_free(frame); } - /* - * Log details of a successful authorization to a service. - * - * Only successful authorizations are logged. For clarity: - * - NTLM bad passwords will be recorded by the above - * - Kerberos decrypt failures need to be logged in gensec_gssapi et al + * Log details of an authentication attempt. + * Successful and unsuccessful attempts are logged. * - * The service may later refuse authorization due to an ACL. + * NOTE: msg_ctx and lp_ctx is optional, but when supplied allows streaming the + * authentication events over the message bus. + */ +void log_authentication_event( const struct auth_usersupplied_info *ui, + NTSTATUS status, + const char *domain_name, + const char *account_name, + const char *unix_username, + struct dom_sid *sid) +{ + /* set the log level */ + int debug_level = AUTH_FAILURE_LEVEL; + + if (NT_STATUS_IS_OK(status)) { + debug_level = AUTH_SUCCESS_LEVEL; + if (dom_sid_equal(sid, &global_sid_Anonymous)) { + debug_level = AUTH_ANONYMOUS_LEVEL; + } + } + + if (CHECK_DEBUGLVLC( DBGC_AUTH_AUDIT, debug_level)) { + log_authentication_event_human_readable(ui, + status, + domain_name, + account_name, + unix_username, + sid, + debug_level); + } + if (CHECK_DEBUGLVLC( DBGC_AUTH_AUDIT_JSON, debug_level)) { + log_authentication_event_json(ui, + status, + domain_name, + account_name, + unix_username, + sid, + debug_level); + } +} + + + +/* + * Log details of a successful authorization to a service, + * in a human readable format. * */ -void log_successful_authz_event(const struct tsocket_address *remote, +static void log_successful_authz_event_human_readable( + const struct tsocket_address *remote, const struct tsocket_address *local, const char *service_description, const char *auth_type, const char *transport_protection, - struct auth_session_info *session_info) + struct auth_session_info *session_info, + int debug_level) { TALLOC_CTX *frame = NULL; @@ -236,16 +701,6 @@ void log_successful_authz_event(const struct tsocket_address *remote, char *remote_str = NULL; /* formatted remote host */ char *local_str = NULL; /* formatted local host */ char sid_buf[DOM_SID_STR_BUFLEN]; - int debug_level = AUTHZ_SUCCESS_LEVEL; - - if (security_token_is_anonymous(session_info->security_token)) { - debug_level = AUTH_ANONYMOUS_LEVEL; - } - - /* set the log level */ - if (!CHECK_DEBUGLVLC( DBGC_AUTH_AUDIT, debug_level)) { - return; - } frame = talloc_stackframe(); @@ -255,9 +710,11 @@ void log_successful_authz_event(const struct tsocket_address *remote, remote_str = tsocket_address_string(remote, frame); local_str = tsocket_address_string(local, frame); - dom_sid_string_buf(&session_info->security_token->sids[0], sid_buf, sizeof(sid_buf)); + dom_sid_string_buf(&session_info->security_token->sids[0], + sid_buf, + sizeof(sid_buf)); - DEBUGC( DBGC_AUTH_AUDIT, AUTHZ_SUCCESS_LEVEL, ( + DEBUGC( DBGC_AUTH_AUDIT, debug_level, ( "Successful AuthZ: [%s,%s] user [%s]\\[%s] [%s]" " at [%s]" " Remote host [%s]" @@ -273,3 +730,49 @@ void log_successful_authz_event(const struct tsocket_address *remote, talloc_free(frame); } + +/* + * Log details of a successful authorization to a service. + * + * Only successful authorizations are logged. For clarity: + * - NTLM bad passwords will be recorded by log_authentication_event + * - Kerberos decrypt failures need to be logged in gensec_gssapi et al + * + * The service may later refuse authorization due to an ACL. + * + * NOTE: msg_ctx and lp_ctx is optional, but when supplied allows streaming the + * authentication events over the message bus. + */ +void log_successful_authz_event(const struct tsocket_address *remote, + const struct tsocket_address *local, + const char *service_description, + const char *auth_type, + const char *transport_protection, + struct auth_session_info *session_info) +{ + int debug_level = AUTHZ_SUCCESS_LEVEL; + + /* set the log level */ + if (security_token_is_anonymous(session_info->security_token)) { + debug_level = AUTH_ANONYMOUS_LEVEL; + } + + if (CHECK_DEBUGLVLC( DBGC_AUTH_AUDIT, debug_level)) { + log_successful_authz_event_human_readable(remote, + local, + service_description, + auth_type, + transport_protection, + session_info, + debug_level); + } + if (CHECK_DEBUGLVLC( DBGC_AUTH_AUDIT_JSON, debug_level)) { + log_successful_authz_event_json(remote, + local, + service_description, + auth_type, + transport_protection, + session_info, + debug_level); + } +} diff --git a/auth/wscript_build b/auth/wscript_build index 732536d686e..30f8bf9e1fb 100644 --- a/auth/wscript_build +++ b/auth/wscript_build @@ -2,7 +2,7 @@ bld.SAMBA_LIBRARY('common_auth', source='auth_sam_reply.c wbc_auth_util.c auth_log.c', - deps='talloc samba-security samba-util util_str_escape LIBTSOCKET', + deps='talloc samba-security samba-util util_str_escape LIBTSOCKET jansson', private_library=True ) diff --git a/auth/wscript_configure b/auth/wscript_configure new file mode 100644 index 00000000000..47943fa1e9c --- /dev/null +++ b/auth/wscript_configure @@ -0,0 +1,7 @@ +#!/usr/bin/env python + +conf.SET_TARGET_TYPE('jansson', 'EMPTY') + +if conf.CHECK_CFG(package='jansson', args='--cflags --libs', + msg='Checking for jansson'): + conf.CHECK_FUNCS_IN('json_object', 'jansson') diff --git a/docs-xml/smbdotconf/logging/loglevel.xml b/docs-xml/smbdotconf/logging/loglevel.xml index d943dd93519..533ba3d4a34 100644 --- a/docs-xml/smbdotconf/logging/loglevel.xml +++ b/docs-xml/smbdotconf/logging/loglevel.xml @@ -41,7 +41,30 @@ <listitem><para><parameter moreinfo="none">dns</parameter></para></listitem> <listitem><para><parameter moreinfo="none">ldb</parameter></para></listitem> <listitem><para><parameter moreinfo="none">tevent</parameter></para></listitem> + <listitem><para><parameter moreinfo="none">auth_audit</parameter></para></listitem> + <listitem><para><parameter moreinfo="none">auth_json_audit</parameter></para></listitem> </itemizedlist> + + <para>Authentication and authorization audit information is logged + under the auth_audit, and if Samba is compiled against the jansson + JSON library, a JSON representation is logged under + auth_json_audit.</para> + + <para>Support is comprehensive for all authentication and authorisation + of user accounts in the Samba Active Directory Domain Controller, + as well as the implicit authentication in password changes. In + the file server, NTLM authentication, SMB and RPC authorization is + covered.</para> + + <para>Log levels for auth_audit and auth_audit_json are:</para> + <itemizedlist> + <listitem><para>2: Authentication Failure</para></listitem> + <listitem><para>3: Authentication Success</para></listitem> + <listitem><para>4: Authorization Success</para></listitem> + <listitem><para>5: Anonymous Authentication and Authorization Success</para></listitem> + </itemizedlist> + + </description> <value type="default">0</value> diff --git a/lib/util/debug.c b/lib/util/debug.c index 009f3629979..f48daf69553 100644 --- a/lib/util/debug.c +++ b/lib/util/debug.c @@ -538,6 +538,7 @@ static const char *default_classname_table[] = { [DBGC_LDB] = "ldb", [DBGC_TEVENT] = "tevent", [DBGC_AUTH_AUDIT] = "auth_audit", + [DBGC_AUTH_AUDIT_JSON] = "auth_json_audit", }; /* diff --git a/lib/util/debug.h b/lib/util/debug.h index 786c80958ec..9d5f438255d 100644 --- a/lib/util/debug.h +++ b/lib/util/debug.h @@ -90,7 +90,7 @@ bool dbghdr( int level, const char *location, const char *func); #define DBGC_LDB 22 #define DBGC_TEVENT 23 #define DBGC_AUTH_AUDIT 24 - +#define DBGC_AUTH_AUDIT_JSON 25 /* So you can define DBGC_CLASS before including debug.h */ #ifndef DBGC_CLASS #define DBGC_CLASS 0 /* override as shown above */ @@ -203,6 +203,7 @@ def configure(conf): conf.RECURSE('ctdb') conf.RECURSE('lib/socket') conf.RECURSE('testsuite/unittests') + conf.RECURSE('auth') conf.SAMBA_CHECK_UNDEFINED_SYMBOL_FLAGS() |