Skip to content

Commit

Permalink
add support for deploying snowcat to a cluster as a job (#28)
Browse files Browse the repository at this point in the history
* add support for deploying snowcat to a cluster as a job

* add github action to build and push container image
  • Loading branch information
amlweems authored Oct 22, 2021
1 parent 39527a7 commit be566b4
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 2 deletions.
39 changes: 39 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ on:

name: Release

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:
goreleaser:
runs-on: ubuntu-latest
Expand All @@ -39,3 +43,38 @@ jobs:
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

build-and-push-image:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write

steps:
- name: Checkout repository
uses: actions/checkout@v2

- name: Log in to the Container registry
uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
with:
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

- name: Build and push Docker image
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
25 changes: 25 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Copyright 2021 Praetorian Security, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

FROM golang AS build
WORKDIR /app
ADD go.* ./
RUN go mod download
ADD . .
RUN CGO_ENABLED=0 go build -trimpath ./cmd/snowcat

FROM alpine
VOLUME /data
COPY --from=build /app/snowcat /bin/
ENTRYPOINT ["/bin/snowcat"]
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,29 @@ directory containing Kubernets YAML files.
./snowcat [options]
```

### Run Snowcat in a cluster as a Job

```shell
# deploy snowcat to your cluster as a Job
$ kubectl -n default apply -f https://raw.githubusercontent.com/praetorian-inc/snowcat/main/deploy/job.yaml
job.batch/snowcat created

# wait a few moments for the scan to complete

# review snowcat logs
$ kubectl -n default logs jobs/snowcat
...
time="2021-10-22T17:47:50Z" level=info msg="running auditor" auditor="Overly Broad Gateway Hosts"
time="2021-10-22T17:47:50Z" level=info msg="running auditor" auditor="Weak Service Account Authentication"
time="2021-10-22T17:47:50Z" level=info msg="found jwt policy" auditor="Weak Service Account Authentication" policy=third-party-jwt
snowcat job complete! use the following command to export the results:

kubectl -n default cp snowcat-46tj5:/data snowcat-results

# download results from the running pod
$ kubectl -n default cp snowcat-46tj5:/data snowcat-results
```

### Get Help

```shell
Expand Down Expand Up @@ -146,6 +169,9 @@ The following configuration options can be specified:
* `--export <directory>` - this flag will cause Snowcat to output the discovered
Kubernetes resources to a directory as YAML files

* `--output <path>` - this flag will cause Snowcat to scan results to the
specified file

* `--istio-version <version>` - if the Istio control plane version is known prior
to running the tool, it can be passed via this flag. Additionally, it binds to
the configuration variable `istio-version` in the configuration file.
Expand All @@ -166,6 +192,10 @@ The following configuration options can be specified:
read-only API ports. It is bound to the configuration variable
`kubelet-addresses`

* `--job-mode` - this flag is used in `deploy/job.yaml` to pause the snowcat binary
and provide information to the user on how to extract results from a running
container. NOTE: this is not useful outside the Job usage scenario.

To set these flags with environment variables, simply uppercase the
configuration variable name, and replace dashes with underscores, for example:
`istio-version` -> `ISTIO_VERSION`
29 changes: 29 additions & 0 deletions deploy/job.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
apiVersion: batch/v1
kind: Job
metadata:
name: snowcat
spec:
backoffLimit: 3
template:
spec:
containers:
- name: snowcat
image: ghcr.io/praetorian-inc/snowcat:latest
imagePullPolicy: Always
command:
- "snowcat"
- "--export=/data"
- "--output=/data/results.json"
- "--format=json"
- "--log-level=debug"
- "--job-mode"
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
restartPolicy: OnFailure
43 changes: 41 additions & 2 deletions pkg/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"os"
"strings"
"time"
Expand Down Expand Up @@ -47,12 +48,21 @@ var (
logLevelFlag string
formatFlag string
exportDirectoryFlag string
outputFileFlag string
istioVersionFlag string
istioNamespaceFlag string
discoveryAddressFlag string
debugzAddressFlag string
kubeletAddressesFlag []string
saveConfFlag bool
jobMode bool
)

const (
jobCompleteMsg = `snowcat job complete! use the following command to export the results:
kubectl -n %s cp %s:%s snowcat-results
`
)

// rootCmd represents the base command when called without any subcommands
Expand Down Expand Up @@ -100,6 +110,9 @@ func init() {
rootCmd.Flags().StringVar(&exportDirectoryFlag, "export", "",
"write discovered resources to the specified export directory as yaml")

rootCmd.Flags().StringVar(&outputFileFlag, "output", "",
"write results to the specified file")

rootCmd.Flags().StringVar(&istioVersionFlag, "istio-version", "",
"the version of the istio control plane")
viper.BindPFlag("istio-version", rootCmd.Flags().Lookup("istio-version"))
Expand All @@ -122,6 +135,9 @@ func init() {

rootCmd.Flags().BoolVarP(&saveConfFlag, "save-config", "s", false,
"whether or not to save discovery to current config file")

rootCmd.Flags().BoolVarP(&jobMode, "job-mode", "", false,
"used when running snowcat as a k8s job, delays exit to allow extracting results")
}

func initConfig() {
Expand Down Expand Up @@ -257,16 +273,24 @@ func RunSnowcat(args []string) {
results = append(results, res...)
}

var out io.WriteCloser
if outputFileFlag != "" {
out, err = os.Create(outputFileFlag)
defer out.Close()
} else {
out = os.Stdout
}

switch formatFlag {
case "json":
enc := json.NewEncoder(os.Stdout)
enc := json.NewEncoder(out)
enc.SetIndent("", " ")
_ = enc.Encode(results)
case "text":
red := color.New(color.FgRed).SprintFunc()
yellow := color.New(color.FgYellow).SprintFunc()
for _, res := range results {
fmt.Printf("%s [%s]: %s\n", red(res.Name), yellow(res.Resource), res.Description)
fmt.Fprintf(out, "%s [%s]: %s\n", red(res.Name), yellow(res.Resource), res.Description)
}
}

Expand All @@ -282,4 +306,19 @@ func RunSnowcat(args []string) {
}).Errorf("could not save configuration")
}
}

if jobMode && exportDirectoryFlag != "" {
podName := os.Getenv("POD_NAME")
if podName == "" {
podName = "<pod>"
}

namespace := os.Getenv("POD_NAMESPACE")
if namespace == "" {
namespace = "<namespace>"
}

fmt.Printf(jobCompleteMsg, namespace, podName, exportDirectoryFlag)
time.Sleep(5 * time.Minute)
}
}

0 comments on commit be566b4

Please sign in to comment.