Skip to content

Commit

Permalink
feat: initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
rfizzle committed Aug 10, 2020
0 parents commit 12c17ee
Show file tree
Hide file tree
Showing 9 changed files with 988 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .gitignore
@@ -0,0 +1,9 @@
*.log
*.log.*
*-collector
.idea
.DS_Store
*.env
*.state
secrets.yml
*.json
58 changes: 58 additions & 0 deletions Dockerfile
@@ -0,0 +1,58 @@
# Accept the Go version for the image to be set as a build argument.
# Default to Go 1.12
ARG GO_VERSION=1.13

# First stage: build the executable.
FROM golang:${GO_VERSION}-alpine AS builder

# Enable Go Modules
ENV GO111MODULE=on

# Install dependencies
RUN apk --no-cache add build-base git bzr mercurial gcc ca-certificates

# Create the user and group files that will be used in the running container to
# run the process as an unprivileged user.
RUN mkdir /user && \
echo 'nobody:x:65534:65534:nobody:/:' > /user/passwd && \
echo 'nobody:x:65534:' > /user/group

# Create collector log directory
RUN mkdir -p /var/log/collector

# Set the working directory outside $GOPATH to enable the support for modules.
WORKDIR /src

# Copy Go Module config
COPY go.mod .
COPY go.sum .

# Download Go Modules
RUN go mod download

# Import the code from the context.
COPY . .

# Build the executable to `/app`. Mark the build as statically linked.
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o app .

# Final stage: the running container.
FROM scratch AS final

# Import the user and group files from the first stage.
COPY --from=builder /user/group /user/passwd /etc/

# Import the Certificate-Authority certificates for enabling HTTPS.
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

# Import the compiled executable from the first stage.
COPY --from=builder src/app /app

# Import logging directory from first stage.
COPY --chown=nobody:nobody --from=builder /var/log/collector /var/log/collector

# Perform any further action as an unprivileged user.
USER nobody:nobody

# Run the compiled binary.
ENTRYPOINT ["/app"]
56 changes: 56 additions & 0 deletions cli.go
@@ -0,0 +1,56 @@
package main

import (
"errors"
"github.com/rfizzle/collector-helpers/outputs"
flag "github.com/spf13/pflag"
"github.com/spf13/viper"
"log"
"strings"
)

func setupCliFlags() error {
viper.SetEnvPrefix("GSC")
viper.AutomaticEnv()
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))

flag.String("state-path", "google.state", "state file path")
flag.Int("schedule", 30, "time in seconds to collect")
flag.String("google-credentials", "", "google service account creds file path")
flag.String("impersonated-user", "", "user to impersonate for API access")
flag.BoolP("verbose", "v", false, "verbose logging")
outputs.InitCLIParams()
flag.Parse()
err := viper.BindPFlags(flag.CommandLine)

if err != nil {
log.Fatalf("Failed parsing flags: %v", err.Error())
}

// Check parameters
if err := checkRequiredParams(); err != nil {
return err
}

return nil
}

func checkRequiredParams() error {
if viper.GetString("state-path") == "" {
return errors.New("missing State File Path param (--state-path)")
}

if viper.GetString("google-credentials") == "" {
return errors.New("missing Google Credentials param (--google-credentials)")
}

if viper.GetString("impersonated-user") == "" {
return errors.New("missing Impersonate User param (--impersonated-user)")
}

if err := outputs.ValidateCLIParams(); err != nil {
return err
}

return nil
}
14 changes: 14 additions & 0 deletions client/auth.go
@@ -0,0 +1,14 @@
package client

type GoogleServiceAccountCredentials struct {
Type string `json:"type"`
ProjectId string `json:"project_id"`
PrivateKeyId string `json:"private_key_id"`
PrivateKey string `json:"private_key"`
ClientEmail string `json:"client_email"`
ClientId string `json:"client_id"`
AuthUri string `json:"auth_uri"`
TokenUri string `json:"token_uri"`
AuthProviderX509CertUrl string `json:"auth_provider_x509_cert_url"`
ClientX509CertUrl string `json:"client_x509_cert_url"`
}
19 changes: 19 additions & 0 deletions client/helpers.go
@@ -0,0 +1,19 @@
package client

import (
"encoding/json"
adminreports "google.golang.org/api/admin/reports/v1"
)

func convertActivityTypeToInterface(items []*adminreports.Activity) []string {
var data []string
for _, val := range items {
// Convert item to json byte array
plain, _ := json.Marshal(val)

// Add string to array
data = append(data, string(plain))
}

return data
}
107 changes: 107 additions & 0 deletions client/main.go
@@ -0,0 +1,107 @@
package client

import (
"context"
"encoding/json"
"errors"
"github.com/tidwall/pretty"
"golang.org/x/oauth2/google"
"golang.org/x/oauth2/jwt"
adminreports "google.golang.org/api/admin/reports/v1"
"io/ioutil"
"net/http"
"os"
)

func BuildClient(credentialFilePath, impersonationEmail string) (*http.Client, error) {
// Open our jsonFile
credentialJsonFile, err := os.Open(credentialFilePath)

// if we os.Open returns an error then handle it
if err != nil {
return nil, errors.New("error reading credential file")
}

// Read credential file
byteValue, err := ioutil.ReadAll(credentialJsonFile)

// Handle credential file read issues
if err != nil {
return nil, errors.New("error reading json from credential file")
}

// Define creds
var creds GoogleServiceAccountCredentials

// unmarshal into object
err = json.Unmarshal(byteValue, &creds)

// return if error during unmarshal
if err != nil {
return nil, errors.New("error parsing json from credential file to struct")
}

conf := &jwt.Config{
Email: creds.ClientEmail,
// The contents of your RSA private key or your PEM file
// that contains a private key.
// If you have a p12 file instead, you
// can use `openssl` to export the private key into a pem file.
//
// $ openssl pkcs12 -in key.p12 -passin pass:notasecret -out key.pem -nodes
//
// The field only supports PEM containers with no passphrase.
// The openssl command will convert p12 keys to passphrase-less PEM containers.
PrivateKey: []byte(creds.PrivateKey),
Scopes: []string{
"https://www.googleapis.com/auth/admin.reports.audit.readonly",
"https://www.googleapis.com/auth/admin.reports.usage.readonly",
},
TokenURL: google.JWTTokenURL,
// If you would like to impersonate a user, you can
// create a transport with a subject. The following GET
// request will be made on the behalf of user@example.com.
// Optional.
Subject: impersonationEmail,
}
// Initiate an http.Client, the following GET request will be
// authorized and authenticated on the behalf of user@example.com.
return conf.Client(context.Background()), nil
}

func ActivitiesList(service *adminreports.Service, eventType, timestamp string, resultsChannel chan<- string) (int, error) {
count := 0
response, err := service.Activities.List("all", eventType).StartTime(timestamp).MaxResults(1000).Do()
if err != nil {
return 0, err
}

// Return if there are no new results
if len(response.Items) == 0 {
return 0, nil
} else {
// Convert to the activity type
tmpData := convertActivityTypeToInterface(response.Items)
count += len(tmpData)
for _, event := range tmpData {
// Ugly print the json into a single lined string
resultsChannel <- string(pretty.Ugly([]byte(event)))
}
}

// Handle paged responses
for response.NextPageToken != "" {
response, err := service.Activities.List("all", eventType).StartTime(timestamp).MaxResults(1000).PageToken(response.NextPageToken).Do()
if err != nil {
return 0, err
}
tmpData := convertActivityTypeToInterface(response.Items)
count += len(tmpData)
for _, event := range tmpData {
// Ugly print the json into a single lined string
resultsChannel <- string(pretty.Ugly([]byte(event)))
}
}

return count, nil
}
12 changes: 12 additions & 0 deletions go.mod
@@ -0,0 +1,12 @@
module github.com/rfizzle/gsuite-collector

go 1.14

require (
github.com/rfizzle/collector-helpers v0.1.0
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.7.1
github.com/tidwall/pretty v1.0.1
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
google.golang.org/api v0.30.0
)

0 comments on commit 12c17ee

Please sign in to comment.