{"id":807,"date":"2024-12-20T10:17:30","date_gmt":"2024-12-20T09:17:30","guid":{"rendered":"https:\/\/ilmarkerm.eu\/blog\/?p=807"},"modified":"2024-12-20T10:28:53","modified_gmt":"2024-12-20T09:28:53","slug":"extracting-pgaudit-data-as-metrics-in-aws","status":"publish","type":"post","link":"https:\/\/ilmarkerm.eu\/blog\/2024\/12\/extracting-pgaudit-data-as-metrics-in-aws\/","title":{"rendered":"Extracting pgAudit data as metrics in AWS"},"content":{"rendered":"\n<p>WARNING! This post contains ClickOps methods.<\/p>\n\n\n\n<p>I have a customer running Autota PostgreSQL in AWS and they wanted to monitor and alert certain database users on how much they read rows from the database.<\/p>\n\n\n\n<p>For auditing PostgreSQL database there is a extension pgAudit, which is also by default installed in Aurora PostgreSQL. <a href=\"https:\/\/docs.aws.amazon.com\/AmazonRDS\/latest\/UserGuide\/Appendix.PostgreSQL.CommonDBATasks.pgaudit.html\" data-type=\"link\" data-id=\"https:\/\/docs.aws.amazon.com\/AmazonRDS\/latest\/UserGuide\/Appendix.PostgreSQL.CommonDBATasks.pgaudit.html\" target=\"_blank\" rel=\"noreferrer noopener\">You can read about how to set it up for your AWS database here.<\/a><\/p>\n\n\n\n<p>To enable auditing only on a specific user<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ALTER USER \"ilmar.kerm\" SET pgaudit.log TO 'all';\nALTER USER \"ilmar.kerm\" SET pgaudit.log_catalog TO 'off';\nALTER USER \"ilmar.kerm\" SET pgaudit.log_parameter TO 'on';\nALTER USER \"ilmar.kerm\" SET pgaudit.log_rows TO 'on';<\/code><\/pre>\n\n\n\n<p>For my use case, the important bit is also to turn ON pgaudit.log_rows.<\/p>\n\n\n\n<p>After that is done, pgAudit will emit a log record into the main PostgreSQL text log, that looks like this:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">2024-12-18 02:25:44 UTC:10.20.30.40(53162):ilmar.kerm@egp:[32766]:LOG:  AUDIT: SESSION,94,1,READ,SELECT,,,\"SELECT id, value, somethingelse FROM \"\"APPLSCHEMA1\"\".\"\"TableName\"\"\",&lt;none&gt;,42<\/pre>\n\n\n\n<p>This audit record tells me that user <strong>&#8220;ilmar.kerm&#8221;<\/strong> executed <strong>SELECT id, value, somethingelse FROM &#8220;APPLSCHEMA1&#8243;.&#8221;TableName&#8221;<\/strong>, and that query returned <strong>42<\/strong> rows.<\/p>\n\n\n\n<p>I now need to extract that value 42 from these audit rows and turn them into CloudWatch metric values.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Cloudwatch Metrics<\/h2>\n\n\n\n<p>To extract metric values from CloudWatch Logs, there are <a href=\"https:\/\/docs.aws.amazon.com\/AmazonCloudWatch\/latest\/logs\/FilterAndPatternSyntaxForMetricFilters.html\" data-type=\"link\" data-id=\"https:\/\/docs.aws.amazon.com\/AmazonCloudWatch\/latest\/logs\/FilterAndPatternSyntaxForMetricFilters.html\" target=\"_blank\" rel=\"noreferrer noopener\">Metric filters<\/a>, but they can only extract values from JSON or SPACE-DELIMITED log records. The pg_audit record above was COMMA-DELIMITED.<\/p>\n\n\n\n<p>Very recently <a href=\"https:\/\/aws.amazon.com\/about-aws\/whats-new\/2024\/11\/amazon-cloudwatch-logs-transform-enrich\/\" data-type=\"link\" data-id=\"https:\/\/aws.amazon.com\/about-aws\/whats-new\/2024\/11\/amazon-cloudwatch-logs-transform-enrich\/\" target=\"_blank\" rel=\"noreferrer noopener\">AWS announced an ability to transform logs (into JSON) on ingestion using pre-built or custom parsers<\/a>. And the transformed logs can also be used to create Metric filters.<\/p>\n\n\n\n<p>Don&#8217;t worry, the transformed log is store in addition to the old format, the old format is still accessible as-is. This ofcourse means that the amount of logs CloudWatch stores for PostgreSQL will double. A thing to keep in mind, and not enable auditing for the busy application users.<\/p>\n\n\n\n<p>NB! The examples below use the dreaded ClickOps methodology, since currently AWS Terraform modules do not support this new feature.<\/p>\n\n\n\n<p>First have to turn on the log transformer:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Open the PostgreSQL database CloudWatch Log Group <strong>\/aws\/rds\/cluster\/CLUSTER_NAME\/postgresql<\/strong><\/li>\n\n\n\n<li>Navigate to <strong>Transformer<\/strong> and click <strong>Create transformer<\/strong><\/li>\n\n\n\n<li>Choose a parser: <strong>Postgres<\/strong><\/li>\n\n\n\n<li>Press <strong>Add processor<\/strong><\/li>\n\n\n\n<li>Processor: <strong>CSV<\/strong> Source: <strong>body<\/strong> Delimiter: <strong>,<\/strong> Quote character: <strong>&#8220;<\/strong><\/li>\n\n\n\n<li>Under Columns it would be possible to give each column under body a proper name, but I&#8217;m not going to, since the transformations apply to all PostgreSQL log records, not just pgAudit.<\/li>\n\n\n\n<li>Press <strong>Save<\/strong><\/li>\n<\/ol>\n\n\n\n<p>Here is how the transformer settings look and also how a new transformed audit record will look like.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/ilmarkerm.eu\/blog\/wp-content\/uploads\/2024\/12\/Screenshot-2024-12-20-at-09.56.44.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"374\" src=\"https:\/\/ilmarkerm.eu\/blog\/wp-content\/uploads\/2024\/12\/Screenshot-2024-12-20-at-09.56.44-1024x374.png\" alt=\"\" class=\"wp-image-810\" srcset=\"https:\/\/ilmarkerm.eu\/blog\/wp-content\/uploads\/2024\/12\/Screenshot-2024-12-20-at-09.56.44-1024x374.png 1024w, https:\/\/ilmarkerm.eu\/blog\/wp-content\/uploads\/2024\/12\/Screenshot-2024-12-20-at-09.56.44-300x109.png 300w, https:\/\/ilmarkerm.eu\/blog\/wp-content\/uploads\/2024\/12\/Screenshot-2024-12-20-at-09.56.44-768x280.png 768w, https:\/\/ilmarkerm.eu\/blog\/wp-content\/uploads\/2024\/12\/Screenshot-2024-12-20-at-09.56.44-1536x561.png 1536w, https:\/\/ilmarkerm.eu\/blog\/wp-content\/uploads\/2024\/12\/Screenshot-2024-12-20-at-09.56.44-2048x747.png 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<p>Every field is beautifully split into JSON attributes that we can extract for metric values. This is what we&#8217;ll do next.<\/p>\n\n\n\n<p>Creating a metric to count all rows affected by audited operations.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Navigate to <strong>Metric filters<\/strong> and press <strong>Create metric filter<\/strong><\/li>\n\n\n\n<li>Create filter pattern: <strong>{ $.body.column_1 = &#8220;AUDIT: SESSION&#8221; }<\/strong><\/li>\n\n\n\n<li>Enable metric filter on transformed logs: <strong>ON<\/strong><\/li>\n\n\n\n<li><strong>Next<\/strong><\/li>\n\n\n\n<li>Filter name: write something yourself<\/li>\n\n\n\n<li><strong>Metric namespace<\/strong> and <strong>Metric name<\/strong> &#8211; write something yourself, this is how the metric will be named under <strong>CloudWatch Metrics<\/strong><\/li>\n\n\n\n<li>Metric value: <strong>$.body.column_10<\/strong><\/li>\n\n\n\n<li>Unit: <strong>Count<\/strong><\/li>\n\n\n\n<li>Dimension name: <strong>operation<\/strong>, Dimension value: <strong>$.body.column_4<\/strong><\/li>\n<\/ol>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/ilmarkerm.eu\/blog\/wp-content\/uploads\/2024\/12\/Screenshot-2024-12-20-at-10.05.58.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"347\" src=\"https:\/\/ilmarkerm.eu\/blog\/wp-content\/uploads\/2024\/12\/Screenshot-2024-12-20-at-10.05.58-1024x347.png\" alt=\"\" class=\"wp-image-812\" srcset=\"https:\/\/ilmarkerm.eu\/blog\/wp-content\/uploads\/2024\/12\/Screenshot-2024-12-20-at-10.05.58-1024x347.png 1024w, https:\/\/ilmarkerm.eu\/blog\/wp-content\/uploads\/2024\/12\/Screenshot-2024-12-20-at-10.05.58-300x102.png 300w, https:\/\/ilmarkerm.eu\/blog\/wp-content\/uploads\/2024\/12\/Screenshot-2024-12-20-at-10.05.58-768x260.png 768w, https:\/\/ilmarkerm.eu\/blog\/wp-content\/uploads\/2024\/12\/Screenshot-2024-12-20-at-10.05.58-1536x520.png 1536w, https:\/\/ilmarkerm.eu\/blog\/wp-content\/uploads\/2024\/12\/Screenshot-2024-12-20-at-10.05.58-2048x694.png 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/ilmarkerm.eu\/blog\/wp-content\/uploads\/2024\/12\/Screenshot-2024-12-20-at-10.10.42.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"924\" src=\"https:\/\/ilmarkerm.eu\/blog\/wp-content\/uploads\/2024\/12\/Screenshot-2024-12-20-at-10.10.42-1024x924.png\" alt=\"\" class=\"wp-image-813\" srcset=\"https:\/\/ilmarkerm.eu\/blog\/wp-content\/uploads\/2024\/12\/Screenshot-2024-12-20-at-10.10.42-1024x924.png 1024w, https:\/\/ilmarkerm.eu\/blog\/wp-content\/uploads\/2024\/12\/Screenshot-2024-12-20-at-10.10.42-300x271.png 300w, https:\/\/ilmarkerm.eu\/blog\/wp-content\/uploads\/2024\/12\/Screenshot-2024-12-20-at-10.10.42-768x693.png 768w, https:\/\/ilmarkerm.eu\/blog\/wp-content\/uploads\/2024\/12\/Screenshot-2024-12-20-at-10.10.42-1536x1385.png 1536w, https:\/\/ilmarkerm.eu\/blog\/wp-content\/uploads\/2024\/12\/Screenshot-2024-12-20-at-10.10.42-2048x1847.png 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Something to keep in mind &#8211; auditing COPY command<\/h2>\n\n\n\n<p>pg_audit always reports that COPY command returns 0 rows. I would alert on all executed COPY commands.<\/p>\n\n\n\n<p>To just count the COPY operations executed, create a new metric filter.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Navigate to <strong>Metric filters<\/strong> and press <strong>Create metric filter<\/strong><\/li>\n\n\n\n<li>Create filter pattern: <strong>{ ($.body.column_1 = &#8220;AUDIT: SESSION&#8221;) &amp;&amp; ($.body.column_4 = &#8220;READ&#8221;) &amp;&amp; ($.body.column_5 = &#8220;COPY&#8221;) }<\/strong><\/li>\n\n\n\n<li>Enable metric filter on transformed logs: <strong>ON<\/strong><\/li>\n\n\n\n<li><strong>Next<\/strong><\/li>\n\n\n\n<li>Filter name: write something yourself<\/li>\n\n\n\n<li><strong>Metric namespace<\/strong> and <strong>Metric name<\/strong> &#8211; write something yourself, this is how the metric will be named under <strong>CloudWatch Metrics<\/strong><\/li>\n\n\n\n<li>Metric value: <strong>1<\/strong><\/li>\n\n\n\n<li>Unit: <strong>Count<\/strong><\/li>\n<\/ol>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>WARNING! This post contains ClickOps methods. I have a customer running Autota PostgreSQL in AWS and they wanted to monitor and alert certain database users on how much they read rows from the database. For auditing PostgreSQL database there is a extension pgAudit, which is also by default installed in Aurora PostgreSQL. You can read [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2],"tags":[70,19],"class_list":["post-807","post","type-post","status-publish","format-standard","hentry","category-blog-entry","tag-aws","tag-postgresql"],"_links":{"self":[{"href":"https:\/\/ilmarkerm.eu\/blog\/wp-json\/wp\/v2\/posts\/807","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ilmarkerm.eu\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/ilmarkerm.eu\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/ilmarkerm.eu\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/ilmarkerm.eu\/blog\/wp-json\/wp\/v2\/comments?post=807"}],"version-history":[{"count":7,"href":"https:\/\/ilmarkerm.eu\/blog\/wp-json\/wp\/v2\/posts\/807\/revisions"}],"predecessor-version":[{"id":817,"href":"https:\/\/ilmarkerm.eu\/blog\/wp-json\/wp\/v2\/posts\/807\/revisions\/817"}],"wp:attachment":[{"href":"https:\/\/ilmarkerm.eu\/blog\/wp-json\/wp\/v2\/media?parent=807"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/ilmarkerm.eu\/blog\/wp-json\/wp\/v2\/categories?post=807"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/ilmarkerm.eu\/blog\/wp-json\/wp\/v2\/tags?post=807"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}