HTTP support for audit logging

Important: The 3.6.x version of the HTTP feature is a technology preview, and is not be relied on in a production environment. The 3.7.x version of audit logging provides the production supported version of HTTP.

Audit logging service version 3.6.x hosts a record ingest REST endpoint available to IBM Cloud Pak foundational services and IBM Cloud Pak applications. Workloads can send CADF log events through HTTP requests.

Audit Logging version 3.7.0 improves the earlier HTTP feature support with bind info for workloads in external namespaces. Additionally, code samples are provided in Golang and Node.js. For more information, see Audit logging code samples for Node.js and Golang.

For all HTTP supported versions, clients are responsible for exception handling for failed requests.

Audit logging components

Audit logging supports the following domain endpoints.

https://common-audit-logging:9880/icp-audit.http (from within ibm-common-services namespace)
https://common-audit-logging.ibm-common-services:9880/icp-audit.http

The ingest endpoint enforces valid JSON is passed in and returns an HTTP status code of 200 on a successful REST call. The Audit logging service ingest endpoint is secured by TLS encryption.

Steps for adoption (version 3.6.x)

Use one of the following options to forward audit logs from your service to the Audit logging service.

Use the audit-server-certs in your deployment

If your deployment is running in a namespace other than ibm-common-services, you must copy the audit-server-certs to your namespace. Use the following command to copy the audit-server-certs secret to your namespace. Replace namespace with the name of your namespace.

oc get secret audit-server-certs --namespace=ibm-common-services --export -o yaml |
oc apply --namespace -f -
  1. Mount the audit-server-certs secret to your pod.
    volumeMounts:
          - mountPath: /etc/audit-tls
            name: audit-server-certs
    volumes:
          - name: audit-server-certs
            secret:
              secretName: audit-server-certs
    
  2. Post audit data to the record ingest server with the supplied TLS certificate signed by the audit-server certificate authority (/etc/audit-tls/certificate.pem), audit-server Certificate (/etc/audit-tls/tls.crt), and audit-server Private Key (/etc/audit-tls/tls.key).

    curl -i -X POST -d '{"typeURI":"http://schemas.dmtf.org/cloud/audit/1.0/event","eventType":"activity","id":"icp:f14704b0-a9dd-11e8-817b-89bcae80625c","action":"read","requestPath":"/identity/api/v1/directory/ldap/56027bb0-a9dd-11e8-9573-0b442b44e932/fetchUsergroups?searchString=%2Ac%2A","initiator":{"typeURI":"service/security/account/user","name":"admin","credential":{"type":"token"},"host":{"address":"icp-management-ingress:8443"}},"target":{"id":"6917371c373f3eb2098a9d7bcd5026052a1c665721a80596c74295ddd9f39ee9\n","name":"platform-identity-management","typeURI":"service/storage/directory"},"observer":{"id":"target"},"severity":"normal","outcome":"success","reason":{"reasonType":"HTTP","reasonCode":200},"eventTime":"2018-08-27T09:45:33.563Z","kubernetes.container_id":"6917371c373f3eb2098a9d7bcd5026052a1c665721a80596c74295ddd9f39ee9\n","kubernetes.container_name":"platform-identity-management","kubernetes.pod":"audit-log-test","kubernetes.namespace":"kube-system","origination":"cli","version":"v1.0"}' https://common-audit-logging:9880/icp-audit.http --cert /etc/audit-tls/tls.crt --key /etc/audit-tls/tls.key  --cacert /etc/audit-tls/ca.crt
    

POST audit logs with the -k (insecure) option from your deployment

  1. You can curl with the -k or --insecure flag to skip certificate validation.

      curl -i -v  -k  "https://common-audit-logging:9880/icp-audit.http" -d '{ "typeURI":"http://schemas.dmtf.org/cloud/audit/1.0/event", "eventType":"create", "id":"33311111-0000-0000-0000-000000000000", "action":"test", "initiator":{ "typeURI":"service/logging/audit", "name":"admin", "credential":{ "type":"token" } }, "target":{ "id":"", "name":"curl-command", "typeURI":"service/logging/audit" }, "observer":{ "id":"target" }, "severity":"normal", "outcome":"success", "reason":{ }, "eventTime":"2020-04-16T15:15:50Z", "kubernetes.container_id":"alt", "kubernetes.container_name":"audit-log-test", "kubernetes.pod":"audit-log-test-784bc7545b-ndvk6", "kubernetes.namespace":"ibm-common-services", "origination":"kubectl", "version":"v1.0" }'
    

Steps for adoption (version 3.7.0)

For information about how to access foundational services by using the bind info, see Bindings customization for ibm-auditlogging-operator. The audit bind info includes the audit server cert secret and a configuration map with the ingest URL definition.

Audit logging code samples for Node.js and Golang

You can use the following sample code to send audit records to audit logging services.

Node.js

var fs = require('fs');
var axios = require('axios');
var uuid = require('uuid');
var express = require('express');
var app = express();
var hostname = '127.0.0.1';
var port = 3000;
  app.get('/',(req,res)=>{
    postAuditRecord();
    res.status(200).send('Audit Record Posted!\n');
  });

  app.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
  });

  var generateRecord =  function() {
    var creationTimestamp = new Date(Date.now());
    var expT = creationTimestamp.toISOString();
    var path = '/resources/things';
    var cont_id = process.env.CONTAINER_ID.trim(); 
    var uuid1 = uuid.v1();
    var status = 'normal';
    var username = 'joe1234';
    var outcome  = 'success';
    var action =  'create';
    var actions = { "request" : "create", "rc" : 200 , "id": "newthing"};
    var useragent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2)  ';

    let cadf = {
      "typeURI": "https://schemas.dmtf.org/cloud/audit/1.0/event",
      "id": uuid1,
      "action": action,
      "requestPath":  path, 
      "initiator": {
        "typeURI": "/service/account/user",
        "name": username,
        "credential": {
        "type":"token"
        },
        "host": {
          "user-agent": useragent,
          "address": "192.1.2.3",
        }
    },
    "target": {
      "id": cont_id,
      "name": "things", 
      "actions": actions,
      "typeURI": "resources"
    },
    "observer": {
      "id": "target"
    },
    "severity" : status,
    "outcome": outcome,
    "reason": {
      "reasonType":"HTTP",
      "reasonCode": 200,
    },
    "eventTime": expT,
    "kubernetes.container_id": cont_id,
    "kubernetes.container_name": process.env.SERVICE_NAME,
    "kubernetes.pod": process.env.POD_NAME || process.env.HOSTNAME,
    "kubernetes.namespace": process.env.POD_NAMESPACE,
    "origination": useragent,
    "version": "v1.0",
    };
    return cadf;
};




var postAuditRecord = function() {
    var options = { 
        ca: fs.readFileSync("/etc/audit-tls/ca.crt"),    
        cert: fs.readFileSync("/etc/audit-tls/tls.crt"),
        key: fs.readFileSync("/etc/audit-tls/tls.key"),
        headers: {'Content-Type': 'application/json'}
    };
    var data=generateRecord();
    axios.post('https://common-audit-logging.ibm-common-services:9880/icp-audit.http',data , options)
    .then((response) => {
      console.log(response);
    }, (error) => {
      console.log(error);
    });
};

Golang

import (
    "bytes"
    "crypto/tls"
    "crypto/x509"
    "fmt"
    "github.com/google/uuid"
    "io/ioutil"
    "log"
    "net/http"
    "os"
    "time"
)

var (
    caFile = "/etc/audit-tls/ca.crt"
    certFile = "/etc/audit-tls/tls.crt"
    keyFile = "/etc/audit-tls/tls.key"
    url = "etc/audit-url/http"
)

func main() {
    auditURL, err := ioutil.ReadFile(url)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Audit logging service: ", string(auditURL))

    // Load client cert
    cert, err := tls.LoadX509KeyPair(certFile, keyFile)
    if err != nil {
        log.Fatal(err)
    }

    // Load CA cert
    caCert, err := ioutil.ReadFile(caFile)
    if err != nil {
        log.Fatal(err)
    }
    caCertPool := x509.NewCertPool()
    caCertPool.AppendCertsFromPEM(caCert)

    // Setup HTTPS client
    tlsConfig := &tls.Config{
        Certificates: []tls.Certificate{cert},
        RootCAs:      caCertPool,
    }
    tlsConfig.BuildNameToCertificate()
    transport := &http.Transport{TLSClientConfig: tlsConfig}
    client := &http.Client{Transport: transport}

    for {
        sendRecord(client, string(auditURL), generateRecord())
        time.Sleep(10 * time.Second)
    }
}

func sendRecord(client *http.Client, url string, record []byte) {
    resp, err := client.Post(url, "application/json", bytes.NewBuffer(record))
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()
    fmt.Println("response Status:", resp.Status)
}

func generateRecord() []byte {
    currentTime := time.Now()
    expT := currentTime.Format(time.RFC3339)
    path := "/resources/things"
    containerId := os.Getenv("CONTAINER_ID")
    uuid1, err := uuid.NewRandom()
    if err != nil {
        log.Fatal(err)
    }
    status := "normal"
    username := "joe1234"
    outcome := "success"
    action :=  "create"
    useragent := "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2)"
    containerName := os.Getenv("SERVICE_NAME")
    namespace := os.Getenv("POD_NAMESPACE")
    var pod string
    if pod = os.Getenv("POD_NAME"); pod == "" {
        os.Getenv("HOSTNAME")
    }
    var cadf = []byte(`{
        "typeURI": "https://schemas.dmtf.org/cloud/audit/1.0/event",
        "eventType": "activity",
        "id": "`+ uuid1.String() + `",
        "action": "` + action + `",
        "requestPath": "` + path + `",
        "initiator": {
            "typeURI": "/service/account/user",
            "name": "` + username + `",
            "credential": {
                "type":"token"
            },
            "host": {
                "user-agent": "` + useragent + `",
                "address": "192.1.2.3"
            }
        },
        "target": {
            "id": "` + containerId + `",
            "name": "things",
            "actions": { 
                "request": "create", 
                "rc": 200,
                "id": "newthing"
            },
            "typeURI": "resources"
        },
        "observer": {
            "id": "target"
        },
        "severity": "` + status + `",
        "outcome": "` + outcome + `",
        "reason": {
            "reasonType":"HTTP",
            "reasonCode": 200
        },
        "eventTime": "`+ expT + `",
        "kubernetes.container_id": "` + containerId + `",
        "kubernetes.container_name": "` + containerName + `",
        "kubernetes.pod": "`+ pod + `",
        "kubernetes.namespace": "` + namespace + `",
        "origination": "` + useragent + `",
        "version": "v1.0"
    }`)
    return cadf
}