IBMi (AS400) fans only - How to Sniff User Access
If you need to monitor user access on your IBM i system, you can use SQL to query the history log.
Note: At the end of this post, you'll find a request directly addressed to you, the readers.
Here’s a simple example of how you can do this:
SELECT MESSAGE_ID,
MESSAGE_TIMESTAMP,
FROM_USER,
MESSAGE_TEXT
FROM TABLE (
QSYS2.HISTORY_LOG_INFO()
)
WHERE message_id IN ('CPF1164', 'CPF1124')
AND FROM_USER IN ('QSECOFR', 'QSYS', 'QSYSOPR')
ORDER BY MESSAGE_TIMESTAMP
In this query:
- QSYS2.HISTORY_LOG_INFO(): This function retrieves information from the history log.
- message_id IN ('CPF1164', 'CPF1124') : Filters the results to include only specific message IDs, which indicate sign-on and sign-off events.
- FROM_USER in ('QSECOFR', 'QSYS', 'QSYSOPR'): Filters the results to include only messages from specific users.
Update 28/07/2024: A Smart Enhancement
Thanks to my reader Greg, we can make an intelligent enhancement to our query. By refining the list of users to those with specific administrative permissions, we ensure more accurate monitoring.
Here’s the improved query:
SELECT MESSAGE_ID,
MESSAGE_TIMESTAMP,
FROM_USER,
MESSAGE_TEXT
FROM TABLE (
QSYS2.HISTORY_LOG_INFO()
)
WHERE message_id IN ('CPF1164', 'CPF1124')
AND FROM_USER IN (SELECT user_name
FROM QSYS2.USER_INFO_BASIC
WHERE POSSTR(special_authorities, '*ALLOBJ') > 0
OR POSSTR(special_authorities, '*SECADM') > 0
OR POSSTR(special_authorities, '*IOSYSCFG') >0)
ORDER BY MESSAGE_TIMESTAMP
This adjustment filters the users to only those with the administrative permissions *ALLOBJ, *SECADM, and *IOSYSCFG.
Thank you Greg.
Update 30/07/2024: Another Smart Enhancement (Improved Performance with JOIN)
A reader suggested an enhancement to the original query for better performance. Instead of using the "IN" operator, we can join the tables. This approach has been tested and proven to return results faster. Below is the updated SQL query:
SELECT history.MESSAGE_ID,
history.MESSAGE_TIMESTAMP,
history.FROM_USER,
history.MESSAGE_TEXT
FROM TABLE (
QSYS2.HISTORY_LOG_INFO()
) history
JOIN QSYS2.USER_INFO_BASIC usrInf
ON history.FROM_USER = usrInf.USER_NAME
WHERE history.MESSAGE_ID IN ('CPF1164', 'CPF1124')
AND (POSSTR(usrInf.SPECIAL_AUTHORITIES, '*ALLOBJ') > 0
OR POSSTR(usrInf.SPECIAL_AUTHORITIES, '*SECADM') > 0
OR POSSTR(usrInf.SPECIAL_AUTHORITIES, '*IOSYSCFG') > 0)
ORDER BY history.MESSAGE_TIMESTAMP;
This query helps you track the login and logout activities of critical system users.
Try running this on your system and see the results.
Starting from the suggested SQL, you can derive countless pieces of information thanks to the QSYS2.HISTORY_LOG_INFO() function - Utilize it as you see fit.
Monitoring Failed Login Attempts
Another important aspect to monitor is failed login attempts. Use the following SQL statements to create a view and retrieve the users who have unsuccessfully attempted to log in.
Create a View for User Information:
CREATE OR REPLACE VIEW yourLib/USR_LIST
AS SELECT *
FROM QSYS2.USER_INFO;
Retrieve Users with Failed Login Attempts
SELECT *
FROM yourLib/USR_LIST
WHERE SIGN_ON_ATTEMPTS_NOT_VALID > 0
ORDER BY SIGN_ON_ATTEMPTS_NOT_VALID DESC;
This will list users who have attempted to log in unsuccessfully, ordered by the number of invalid sign-on attempts.
Feel free to comment below if you have any questions or suggestions.
I appreciate all the comments made on this blog.
It's been a long while since I was told this, so I don't remember who told me, but, supposedly, creating your own views on a QSYS2 table is not advised. I dunno if it has to do with the QSYS2 table being replaced when the OS is upgraded? And if the upgrade can't replace it because there is a user view on it, the upgrade fails?
ReplyDeleteYou are right! To avoid such problems, it is generally recommended to create custom views and tables in QTEMP library, or ensure that you delete custom views and tables after the job is done. This practice helps prevent conflicts during system updates and maintains better control over custom objects.
DeleteThank you for bringing this up!
To qualify the list to users that have "administrative permissions", you can replace your clause AND FROM_USER IN ('QSECOFR', 'QSYS', 'QSYSOPR')
ReplyDeletewith
AND FROM_USER in (
select user_name
from USER_INFO_BASIC
where posstr(special_authorities, '*ALLOBJ') > 0
or posstr(special_authorities, '*SECADM') > 0
or posstr(special_authorities, '*IOSYSCFG') > 0
)
Hi Greg,
DeleteI have updated my blog post to include your enhancement, ensuring that other readers can benefit from your expertise as well.
Thanks for your valuable contribution. It is greatly appreciated!
In order to select users with "administrative permissions", you can replace the clause
ReplyDeleteAND FROM_USER IN ('QSECOFR', 'QSYS', 'QSYSOPR')
with
AND FROM_USER IN (
select user_name
from USER_INFO_BASIC
where posstr(special_authorities, '*ALLOBJ') > 0
or posstr(special_authorities, '*SECADM') > 0
or posstr(special_authorities, '*IOSYSCFG') > 0
)
Hi Greg,
DeleteI have updated my blog post to include your enhancement, ensuring that other readers can benefit from your expertise as well.
Thanks for your valuable contribution. It is greatly appreciated!
Using an "IN" operating instead of a join will be a performance hit. Trying joining the two tables on the FROM_USER and the associated user_name column in the 2nd table. I think you'll see that it returns much faster.
ReplyDeleteThank you for your insightful suggestion! Implementing a join instead of an "IN" operator has indeed improved the performance of the query significantly.
DeleteI have updated my blog post to include your enhancement, ensuring that other readers can benefit from your expertise as well.
Your advice is greatly appreciated!
Nice SQL's. I would suggest that wherever the "user_info_basic" line is displayed, it has to be qualified with qsys2 like this "qsys2.user_info_basic". I had to do that to get a couple of these to run.
ReplyDeleteThank you so much for the feedback, M. Mayer! I appreciate your suggestion to qualify the "USER_INFO_BASIC" table with "QSYS2". It's a great point, and I'll be sure to update the SQL examples to reflect this improvement. Your input helps ensure that the queries run smoothly for everyone. Thanks again!
DeleteWhere "USER_INFO_BASIC" is listed on the SQL's, qualifiy it with QSYS2. "QSYS2.USER_INFO_BASIC"
ReplyDeleteThank you so much for the feedback, M. Mayer! I appreciate your suggestion to qualify the "USER_INFO_BASIC" table with "QSYS2". It's a great point, and I'll be sure to update the SQL examples to reflect this improvement. Your input helps ensure that the queries run smoothly for everyone. Thanks again!
Delete