Skip to content

Commit

Permalink
add audit log support
Browse files Browse the repository at this point in the history
  • Loading branch information
aiwantaozi authored and Alena Prokharchyk committed Jul 16, 2018
1 parent 23e4be9 commit 4359ac7
Show file tree
Hide file tree
Showing 8 changed files with 344 additions and 62 deletions.
34 changes: 23 additions & 11 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/rancher/kontainer-engine/service"
"github.com/rancher/norman/leader"
"github.com/rancher/rancher/pkg/audit"
"github.com/rancher/rancher/pkg/auth/providers/common"
"github.com/rancher/rancher/pkg/auth/tokens"
"github.com/rancher/rancher/pkg/clustermanager"
Expand All @@ -19,16 +20,21 @@ import (
)

type Config struct {
ACMEDomains []string
AddLocal string
Embedded bool
KubeConfig string
HTTPListenPort int
HTTPSListenPort int
K8sMode string
Debug bool
NoCACerts bool
ListenConfig *v3.ListenConfig
ACMEDomains []string
AddLocal string
Embedded bool
KubeConfig string
HTTPListenPort int
HTTPSListenPort int
K8sMode string
Debug bool
NoCACerts bool
ListenConfig *v3.ListenConfig
AuditLogPath string
AuditLogMaxage int
AuditLogMaxsize int
AuditLogMaxbackup int
AuditLevel int
}

func buildScaledContext(ctx context.Context, kubeConfig rest.Config, cfg *Config) (*config.ScaledContext, *clustermanager.Manager, error) {
Expand Down Expand Up @@ -77,7 +83,9 @@ func Run(ctx context.Context, kubeConfig rest.Config, cfg *Config) error {
return err
}

if err := server.Start(ctx, cfg.HTTPListenPort, cfg.HTTPSListenPort, scaledContext, clusterManager); err != nil {
auditLogWriter := audit.NewLogWriter(cfg.AuditLogPath, cfg.AuditLevel, cfg.AuditLogMaxage, cfg.AuditLogMaxbackup, cfg.AuditLogMaxsize)

if err := server.Start(ctx, cfg.HTTPListenPort, cfg.HTTPSListenPort, scaledContext, clusterManager, auditLogWriter); err != nil {
return err
}

Expand Down Expand Up @@ -109,6 +117,10 @@ func Run(ctx context.Context, kubeConfig rest.Config, cfg *Config) error {
})

<-ctx.Done()

if auditLogWriter != nil {
auditLogWriter.Output.Close()
}
return ctx.Err()
}

Expand Down
36 changes: 36 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,36 @@ func main() {
Name: "no-cacerts",
Usage: "Skip CA certs population in settings when set to true",
},
cli.StringFlag{
Name: "audit-log-path",
EnvVar: "AUDIT_LOG_PATH",
Value: "/var/log/auditlog/rancher-api-audit.log",
Usage: "Log path for Rancher Server API. Default path is /var/log/auditlog/rancher-api-audit.log",
},
cli.IntFlag{
Name: "audit-log-maxage",
Value: 10,
EnvVar: "AUDIT_LOG_MAXAGE",
Usage: "Defined the maximum number of days to retain old audit log files",
},
cli.IntFlag{
Name: "audit-log-maxbackup",
Value: 10,
EnvVar: "AUDIT_LOG_MAXBACKUP",
Usage: "Defines the maximum number of audit log files to retain",
},
cli.IntFlag{
Name: "audit-log-maxsize",
Value: 100,
EnvVar: "AUDIT_LOG_MAXSIZE",
Usage: "Defines the maximum size in megabytes of the audit log file before it gets rotated, default size is 100M",
},
cli.IntFlag{
Name: "audit-level",
Value: 0,
EnvVar: "AUDIT_LEVEL",
Usage: "Audit log level: 0 - disable audit log, 1 - log event metadata, 2 - log event metadata and request body, 3 - log event metadata, request body and response body",
},
}

app.Action = func(c *cli.Context) error {
Expand All @@ -108,6 +138,12 @@ func main() {

config.ACMEDomains = c.GlobalStringSlice("acme-domain")
config.NoCACerts = c.Bool("no-cacerts")

config.AuditLevel = c.Int("audit-level")
config.AuditLogPath = c.String("audit-log-path")
config.AuditLogMaxage = c.Int("audit-log-maxage")
config.AuditLogMaxbackup = c.Int("audit-log-maxbackup")
config.AuditLogMaxsize = c.Int("audit-log-maxsize")
initLogs(c, config)
return run(config)
}
Expand Down
13 changes: 11 additions & 2 deletions package/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ ENV CATTLE_UI_PATH /usr/share/rancher/ui
ENV CATTLE_UI_VERSION 2.0.61
ENV CATTLE_CLI_VERSION v2.0.3

RUN mkdir -p /var/log/auditlog
ENV AUDIT_LOG_PATH /var/log/auditlog/rancher-api-audit.log
ENV AUDIT_LOG_MAXAGE 10
ENV AUDIT_LOG_MAXBACKUP 10
ENV AUDIT_LOG_MAXSIZE 100
ENV AUDIT_LEVEL 0

RUN mkdir -p /usr/share/rancher/ui && \
cd /usr/share/rancher/ui && \
curl -sL https://releases.rancher.com/ui/${CATTLE_UI_VERSION}.tar.gz | tar xvzf - --strip-components=1 && \
Expand All @@ -35,9 +42,11 @@ ENV CATTLE_CLI_URL_WINDOWS https://releases.rancher.com/cli/${CATTLE_CLI_VERSION

ARG VERSION=dev
ENV CATTLE_SERVER_VERSION ${VERSION}
COPY rancher /usr/bin/
COPY entrypoint.sh rancher /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh

ENV CATTLE_AGENT_IMAGE rancher/rancher-agent:${VERSION}
ENV CATTLE_SERVER_IMAGE rancher/rancher
VOLUME /var/lib/rancher
ENTRYPOINT ["tini", "--", "rancher", "--http-listen-port=80", "--https-listen-port=443"]

ENTRYPOINT ["entrypoint.sh"]
4 changes: 4 additions & 0 deletions package/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/bash
set -e

exec tini -- rancher --http-listen-port=80 --https-listen-port=443 --audit-log-path=${AUDIT_LOG_PATH} --audit-level=${AUDIT_LEVEL} --audit-log-maxage=${AUDIT_LOG_MAXAGE} --audit-log-maxbackup=${AUDIT_LOG_MAXBACKUP} --audit-log-maxsize=${AUDIT_LOG_MAXSIZE}
173 changes: 173 additions & 0 deletions pkg/audit/writer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package audit

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"time"

lumberjack "gopkg.in/natefinch/lumberjack.v2"
k8stypes "k8s.io/apimachinery/pkg/types"
utilnet "k8s.io/apimachinery/pkg/util/net"
)

const (
contentTypeJSON = "application/json"
)

const (
levelNull = iota
levelMetadata
levelRequest
levelRequestResponse
)

var (
auditLevel = map[int]string{
levelMetadata: "MetadataLevel",
levelRequest: "RequestLevel",
levelRequestResponse: "RequestResponseLevel",
}

bodyMethods = map[string]bool{
http.MethodPut: true,
http.MethodPost: true,
}
)

const (
requestReceived = "RequestReceived"
responseComplete = "ResponseComplete"
)

type Log struct {
AuditID k8stypes.UID `json:"auditID,omitempty"`
RequestURI string `json:"requestURI,omitempty"`
RequestBody interface{} `json:"requestBody,omitempty"`
ResponseBody interface{} `json:"responseBody,omitempty"`
ResponseStatus string `json:"responseStatus,omitempty"`
SourceIPs []string `json:"sourceIPs,omitempty"`
User *userInfo `json:"user,omitempty"`
UserAgent string `json:"userAgent,omitempty"`
Verb string `json:"verb,omitempty"`
Stage string `json:"stage,omitempty"`
StageTimestamp string `json:"stageTimestamp,omitempty"`
}

type userInfo struct {
Name string `json:"name,omitempty"`
Group []string `json:"group,omitempty"`
}

type LogWriter struct {
Level int
Output *lumberjack.Logger
}

func NewLogWriter(path string, level, maxAge, maxBackup, maxSize int) *LogWriter {
if path == "" || level == levelNull {
return nil
}

return &LogWriter{
Level: level,
Output: &lumberjack.Logger{
Filename: path,
MaxAge: maxAge,
MaxBackups: maxBackup,
MaxSize: maxSize,
},
}
}

func (a *LogWriter) LogRequest(req *http.Request, auditID k8stypes.UID, authed bool, contentType, user string, group []string) error {
al := &Log{
AuditID: auditID,
Stage: requestReceived,
StageTimestamp: time.Now().Format("2006-01-02 15:04:05 -0700"),
RequestURI: req.RequestURI,
Verb: req.Method,
}

ips := utilnet.SourceIPs(req)
var sourceIPs []string
for i := range ips {
sourceIPs = append(sourceIPs, ips[i].String())
}
al.SourceIPs = sourceIPs

if authed {
al.User = &userInfo{
Name: user,
Group: group,
}
}

var buffer bytes.Buffer

alByte, err := json.Marshal(al)
if err != nil {
return err
}
buffer.Write(bytes.TrimRight(alByte, "}\n"))

if a.Level >= levelRequest && bodyMethods[req.Method] && contentType == contentTypeJSON {
reqBody, err := readBodyWithoutLosingContent(req)
if err != nil {
return err
}
buffer.WriteString(`,"requestBody":`)
buffer.Write(bytes.TrimRight(reqBody, "\n"))
}

buffer.WriteString("}\n")
_, err = a.Output.Write(buffer.Bytes())
return err
}

func (a *LogWriter) LogResponse(resBody []byte, auditID k8stypes.UID, statusCode int, contentType string) error {
if a.Level < levelRequestResponse {
return nil
}

al := &Log{
AuditID: auditID,
Stage: responseComplete,
StageTimestamp: time.Now().Format("2006-01-02 15:04:05 -0700"),
ResponseStatus: fmt.Sprint(statusCode),
}

var buffer bytes.Buffer
alByte, err := json.Marshal(al)
if err != nil {
return err
}

buffer.Write(bytes.TrimRight(alByte, "}\n"))

if contentType == contentTypeJSON {
buffer.WriteString(`,"responseBody":`)
buffer.Write(bytes.TrimRight(resBody, "\n"))
}

buffer.WriteString("}\n")

_, err = a.Output.Write(buffer.Bytes())
return err
}

func readBodyWithoutLosingContent(req *http.Request) ([]byte, error) {
if !bodyMethods[req.Method] {
return nil, nil
}

bodyBytes, err := ioutil.ReadAll(req.Body)
if err != nil {
return nil, err
}
req.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))

return bodyBytes, nil
}
46 changes: 0 additions & 46 deletions pkg/auth/requests/filter.go

This file was deleted.

Loading

0 comments on commit 4359ac7

Please sign in to comment.