Skip to content

Commit

Permalink
Merge pull request #2 from bmaynard/feature/watcher
Browse files Browse the repository at this point in the history
Record change times for ConfigMaps and Secrets
  • Loading branch information
bmaynard committed Jul 8, 2020
2 parents 3f15b8d + e6e4319 commit 8e63b14
Show file tree
Hide file tree
Showing 18 changed files with 716 additions and 44 deletions.
25 changes: 14 additions & 11 deletions .github/workflows/build.yml → .github/workflows/build.yaml
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
name: Build Binary
name: Build Binary + Unit Tests
on: [push]

jobs:
build:
name: Build
runs-on: ubuntu-latest
#strategy:
# matrix:
# build and publish in parallel: linux/386, linux/amd64, windows/386, windows/amd64, darwin/386, darwin/amd64
# goos: [linux, windows, darwin]
# goarch: ["386", amd64]
strategy:
matrix:
goos: [linux, windows, darwin]
goarch: ["386", amd64]
steps:
- name: Set up Go 1.x
uses: actions/setup-go@v2
Expand All @@ -23,14 +22,18 @@ jobs:
- name: Get dependencies
run: |
go get -v -t -d ./...
if [ -f Gopkg.toml ]; then
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
dep ensure
fi
- name: Build
run: go build -v .

env:
GOARCH: ${{ matrix.goarch }}
GOOS: ${{ matrix.goos }}
tests:
name: Unit Tests
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- uses: engineerd/setup-kind@v0.4.0
- name: Testing
run: |
Expand Down
80 changes: 80 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
on: release
name: Build Release
jobs:
release-linux-386:
name: release linux/386
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: compile and release
uses: ngs/go-release.action@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GOARCH: "386"
GOOS: linux
release-linux-amd64:
name: release linux/amd64
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: compile and release
uses: ngs/go-release.action@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GOARCH: amd64
GOOS: linux
release-linux-arm:
name: release linux/386
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: compile and release
uses: ngs/go-release.action@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GOARCH: "arm"
GOOS: linux
release-linux-arm64:
name: release linux/amd64
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: compile and release
uses: ngs/go-release.action@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GOARCH: arm64
GOOS: linux
release-darwin-amd64:
name: release darwin/amd64
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: compile and release
uses: ngs/go-release.action@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GOARCH: amd64
GOOS: darwin
release-windows-386:
name: release windows/386
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: compile and release
uses: ngs/go-release.action@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GOARCH: "386"
GOOS: windows
release-windows-amd64:
name: release windows/amd64
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: compile and release
uses: ngs/go-release.action@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GOARCH: amd64
GOOS: windows
15 changes: 15 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM golang:alpine AS build-env
RUN apk --no-cache add build-base git gcc
ADD . /src
RUN cd /src && go build -o kubevol


FROM alpine
WORKDIR /app
COPY --from=build-env /src/kubevol /app/

EXPOSE 8080

RUN mkdir -p /app/mocks

CMD ["/app/kubevol", "watch"]
29 changes: 16 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
# kubevol

This is a simple application that queries all pods for an attached volume or see all the volumes attached to each pod by specific type (eg: ConfigMap, Secret).
Kubevol allows you to audit all your Kubernetes pods for an attached volume or see all the volumes attached to each pod by a specific type (eg: ConfigMap, Secret).

Features:

- Query for ConfigMaps and Secrets (future support coming for other types of volumes)
- Kubernetes controller to watch and record changes to ConfigMaps and Secrets
- Filter by namespace
- Filter by a specific object name
- See if attached volume is outdated
- Limited support, can only detect if configmap was deleted after pod was created
- See if attached volume has a stale version attached

## Install
## Installation

Currently you need to build the binary yourself which you can accomplish with the following steps:
You can download the latest release from [Releases](https://github.com/bmaynard/kubevol/releases).

```
git clone git@github.com:bmaynard/kubevol.git
cd kubevol
go build
./kubevol --help
## Watch And Record Changes

Since Kubernetes doesn't keep track of when a `Secret` or `Configmap` was updated, `kubevol` has a Kubernetes controller that will watch for all changes and will record the last modified date. This then gives `kubevol` the ability to detect if an attached `Secret` or `Configmap` is outdated.

To install the watch controller, run:

```bash
$ kubectl apply -f https://raw.githubusercontent.com/bmaynard/kubevol/master/deployment/manifest.yaml
```

### Configuration
Expand All @@ -34,14 +37,14 @@ kubeconfig: /path/to/kube/config
## Sample Output

```
There are 1 pods in the cluster
$ kubevol secret
There are 12 pods in the cluster
Searching for pods that have a Secret attached
+------------------+----------+-----------------------+-----------------------+-------------+
| NAMESPACE | POD NAME | SECRET NAME | VOLUME NAME | OUT OF DATE |
+------------------+----------+-----------------------+-----------------------+-------------+
| kubevol-test-run | redis | redis-secret | redis-secret | Unknown |
| kubevol-test-run | redis | redis-secret | redis-secret | No |
| kubevol-test-run | redis | redis-secret-outdated | redis-secret-outdated | Yes |
| kubevol-test-run | redis | default-token-nd4wr | default-token-nd4wr | Unknown |
+------------------+----------+-----------------------+-----------------------+-------------+
```
14 changes: 14 additions & 0 deletions build-publish-docker.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/bash

if [ -z "$TAG" ]
then
echo "\$TAG has not been supplied"
exit 1
fi

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"

cd $DIR

docker build -t bmaynard/kubevol-watch:$TAG .
docker push bmaynard/kubevol-watch:$TAG
30 changes: 27 additions & 3 deletions cmd/configmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@ package cmd

import (
"fmt"
"strconv"
"time"

"github.com/bmaynard/kubevol/pkg/core"
"github.com/bmaynard/kubevol/pkg/watch"
"github.com/fatih/color"
"github.com/spf13/cobra"

"github.com/jedib0t/go-pretty/v6/table"
)

func NewConfigMapCommand(k core.KubeData) *cobra.Command {
func NewConfigMapCommand(f *core.Factory, k *core.KubeData) *cobra.Command {
var cmd = &cobra.Command{
Use: "configmap",
Short: "Find all pods that have a specific ConfigMap attached",
Expand All @@ -25,14 +28,19 @@ func NewConfigMapCommand(k core.KubeData) *cobra.Command {
}

ui := core.SetupTable(table.Row{"Namespace", "Pod Name", "ConfigMap Name", "Volume Name", "Out of Date"}, cmd.OutOrStdout())
configmapTracker, err := k.GetConfigMap(watch.WatchConfigMapTrackerName, watch.WatchNamespace)

if err != nil {
f.Logger.Error(err)
}

for _, pod := range pods.Items {
podName := pod.ObjectMeta.Name
namespace := pod.ObjectMeta.Namespace
_, err := k.GetPod(podName, namespace)

if err != nil {
panic(err.Error())
f.Logger.Error(err)
}

podCreationTime := pod.ObjectMeta.CreationTimestamp.Time
Expand All @@ -41,12 +49,28 @@ func NewConfigMapCommand(k core.KubeData) *cobra.Command {
if volume.ConfigMap != nil {
if objectName == "" || (volume.ConfigMap != nil && volume.ConfigMap.LocalObjectReference.Name == objectName) {
configMap, err := k.GetConfigMap(volume.ConfigMap.LocalObjectReference.Name, namespace)
outOfDate := color.YellowString("Unknown")
trackerName := watch.GetConfigMapKey(namespace, volume.ConfigMap.LocalObjectReference.Name)
var outOfDate string

if configmapTracker.CreationTimestamp.Time.Before(configMap.ObjectMeta.CreationTimestamp.Time) {
outOfDate = color.GreenString("No")
} else {
outOfDate = color.YellowString("Unknown")
}

if err != nil || configMap.ObjectMeta.CreationTimestamp.Time.After(podCreationTime) {
outOfDate = color.RedString("Yes")
}

if updatedTime, ok := configmapTracker.Data[trackerName]; ok {
parsedTime, err := strconv.ParseInt(updatedTime, 10, 64)
if err == nil && configMap.ObjectMeta.CreationTimestamp.Time.Before(time.Unix(parsedTime, 0)) {
outOfDate = color.RedString("Yes")
} else {
outOfDate = color.RedString("No")
}
}

ui.AppendRow([]table.Row{
{color.BlueString(namespace), podName, volume.ConfigMap.LocalObjectReference.Name, volume.Name, outOfDate},
})
Expand Down
9 changes: 5 additions & 4 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,17 @@ func NewKubevolApp() *cobra.Command {
initConfig()

factory := core.NewDepsFactory()
coreClient, err := factory.CoreClient(viper.GetString("kubeconfig"))
coreClient, err := factory.CoreClient()

if err != nil {
panic(err.Error())
factory.Logger.Fatal(err)
}

kubeData := core.NewKubeData(coreClient)

rootCmd.AddCommand(NewConfigMapCommand(*kubeData))
rootCmd.AddCommand(NewSecretCommand(*kubeData))
rootCmd.AddCommand(NewConfigMapCommand(factory, kubeData))
rootCmd.AddCommand(NewSecretCommand(factory, kubeData))
rootCmd.AddCommand(NewWatchCommand(factory))

return rootCmd
}
Expand Down
28 changes: 26 additions & 2 deletions cmd/secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@ package cmd

import (
"fmt"
"strconv"
"time"

"github.com/bmaynard/kubevol/pkg/core"
"github.com/bmaynard/kubevol/pkg/watch"
"github.com/fatih/color"
"github.com/spf13/cobra"

"github.com/jedib0t/go-pretty/v6/table"
)

func NewSecretCommand(k core.KubeData) *cobra.Command {
func NewSecretCommand(f *core.Factory, k *core.KubeData) *cobra.Command {
var cmd = &cobra.Command{
Use: "secret",
Short: "Find all pods that have a specific Secret attached",
Expand All @@ -25,6 +28,11 @@ func NewSecretCommand(k core.KubeData) *cobra.Command {
}

ui := core.SetupTable(table.Row{"Namespace", "Pod Name", "Secret Name", "Volume Name", "Out of Date"}, cmd.OutOrStdout())
secretTracker, err := k.GetConfigMap(watch.WatchSecretTrackerName, watch.WatchNamespace)

if err != nil {
f.Logger.Error(err)
}

for _, pod := range pods.Items {
podName := pod.ObjectMeta.Name
Expand All @@ -41,12 +49,28 @@ func NewSecretCommand(k core.KubeData) *cobra.Command {
if volume.Secret != nil {
if objectName == "" || (volume.Secret != nil && volume.Secret.SecretName == objectName) {
secret, err := k.GetSecret(volume.Secret.SecretName, namespace)
outOfDate := color.YellowString("Unknown")
trackerName := watch.GetConfigMapKey(namespace, volume.Secret.SecretName)
var outOfDate string

if secretTracker.CreationTimestamp.Time.Before(secret.ObjectMeta.CreationTimestamp.Time) {
outOfDate = color.GreenString("No")
} else {
outOfDate = color.YellowString("Unknown")
}

if err != nil || secret.ObjectMeta.CreationTimestamp.Time.After(podCreationTime) {
outOfDate = color.RedString("Yes")
}

if updatedTime, ok := secretTracker.Data[trackerName]; ok {
parsedTime, err := strconv.ParseInt(updatedTime, 10, 64)
if err == nil && secret.ObjectMeta.CreationTimestamp.Time.Before(time.Unix(parsedTime, 0)) {
outOfDate = color.RedString("Yes")
} else {
outOfDate = color.RedString("No")
}
}

ui.AppendRow([]table.Row{
{color.BlueString(namespace), podName, volume.Secret.SecretName, volume.Name, outOfDate},
})
Expand Down

0 comments on commit 8e63b14

Please sign in to comment.