Skip to content

Commit

Permalink
Merge pull request #18 from tuxerrante/dev
Browse files Browse the repository at this point in the history
#18 - Manage custom labels, validate profile content, manage SIGTERM
  • Loading branch information
tuxerrante authored May 15, 2023
2 parents d5a4b87 + 6e10b49 commit 5d23662
Show file tree
Hide file tree
Showing 19 changed files with 258 additions and 77 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ on:
workflow_dispatch:

env:
GO_VERSION: '1.19'
GO_VERSION: '1.20'
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
*.so
*.dylib

.go/

# Docker image files
*.tar.gz
*.tar.xz
Expand All @@ -23,4 +25,7 @@
.errors/

# Go tests
coverage.out
coverage.out

# Secret env vars
config/secrets
88 changes: 44 additions & 44 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,44 +1,44 @@
# --- build stage
FROM golang:1.20 AS builder

WORKDIR /builder/app
COPY go/src/app/ .
COPY go/src/tests/ /builder/tests/
COPY go.mod .

RUN go get -d -v . &&\
go build -v -o /go/bin/app .

RUN go test -v -coverprofile=coverage.out -covermode=atomic
# go tool cover -func=coverage.out


# --- Publish test coverage results
FROM scratch as test-coverage
COPY --from=builder /builder/app/coverage.out .


# --- Production image
FROM ubuntu:latest
LABEL Name=kapparmor
LABEL Author="Affinito Alessandro"

WORKDIR /app

RUN apt-get update &&\
apt-get upgrade -y &&\
apt-get install --no-install-recommends --yes apparmor apparmor-utils &&\
rm -rf /var/lib/apt/lists/* &&\
mkdir --parent --verbose /etc/apparmor.d/custom

COPY --from=builder /go/bin/app /app/
COPY ./charts/kapparmor/profiles /app/profiles/

ARG PROFILES_DIR
ARG POLL_TIME

ENV PROFILES_DIR=$PROFILES_DIR
ENV POLL_TIME=$POLL_TIME

USER root
CMD ./app
# --- build stage
FROM golang:1.20 AS builder

WORKDIR /builder/app
COPY go/src/app/ .
COPY go/src/tests/ /builder/tests/
COPY go.mod .

RUN go get -d -v . &&\
go build -v -o /go/bin/app .

RUN go test -v -coverprofile=coverage.out -covermode=atomic ./...
# go tool cover -func=coverage.out


# --- Publish test coverage results
FROM scratch as test-coverage
COPY --from=builder /builder/app/coverage.out .


# --- Production image
FROM ubuntu:latest
LABEL Name=kapparmor
LABEL Author="Affinito Alessandro"

WORKDIR /app

RUN apt-get update &&\
apt-get upgrade -y &&\
apt-get install --no-install-recommends --yes apparmor apparmor-utils &&\
rm -rf /var/lib/apt/lists/* &&\
mkdir --parent --verbose /etc/apparmor.d/custom

COPY --from=builder /go/bin/app /app/
COPY ./charts/kapparmor/profiles /app/profiles/

ARG PROFILES_DIR
ARG POLL_TIME

ENV PROFILES_DIR=$PROFILES_DIR
ENV POLL_TIME=$POLL_TIME

USER root
ENTRYPOINT ["./app"]
16 changes: 10 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,19 @@ helm upgrade kapparmor --install --atomic --timeout 120s --debug --set image.tag
```

## Known limitations
- Profiles names are checked on the first line, so if there is some include before that would fail
- Profile names have to start with 'custom.' and to be equal as the filename containing it
- There could be issues if you start the daemonsets on "dirty" nodes, where some old custom profiles were left after stopping or uninstalling Kapparmor. E.g: you stop the pods and then redeploy the app with an empty profiles configmap without removing the previous custom profiles: Kapparmor will try to remove the old profiles but it could fail since there is no definition of them anymore.
- Constraint: Profiles are validated on the "`profile`" keyword presence before of a opening curly bracket `{`.
It must be a [unattached profiles](https://documentation.suse.com/sles/15-SP1/html/SLES-all/cha-apparmor-profiles.html#sec-apparmor-profiles-types-unattached).
- Profile names have to start with 'custom.' and to be equal as the filename containing it.
- There could be issues if you start the daemonsets on "dirty" nodes, where some old custom profiles were left after stopping or uninstalling Kapparmor.
E.G: By default if you delete a pod all the profiles should be automatically deleted from that node, but the app crashes during the process.

- Not a limitation relative to this project, but if you deny write access in the /bin folder of a privileged container it could not be deleted by Kubernetes even after 'kubectl delete'. The command will succeed but the pod will stay in Terminating state.

## ToDo:
- Intercept Term signal and uninstall profiles before the Helm chart deletion completes.
- Implement the [controller-runtime](https://pkg.go.dev/sigs.k8s.io/controller-runtime#section-readme) design pattern through [Kubebuilder](https://book.kubebuilder.io/quick-start.html).

- [X] Intercept Term signal and uninstall profiles before the Helm chart deletion completes.
- ⚠️ Implement the [controller-runtime](https://pkg.go.dev/sigs.k8s.io/controller-runtime#section-readme) design pattern through [Kubebuilder](https://book.kubebuilder.io/quick-start.html).
- 😁 Find funnier quotes for app starting and ending message (David Zucker, Monty Python, Woody Allen...).
- 🌱 Make the ticker loop thread safe: skip running a new loop if previous run is still ongoing.

## Testing
[There is a whole project meant to be a demo for this one](https://github.com/tuxerrante/kapparmor-demo), have fun.
Expand Down
Empty file removed azure-pipelines.yml
Empty file.
34 changes: 34 additions & 0 deletions build/build-and-deploy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/bin/bash
set -e

. ./build/build-app.sh
. ./config/secrets

echo $GHCR_TOKEN | docker login -u "$(git config user.email)" --password-stdin ghcr.io
docker push ghcr.io/tuxerrante/kapparmor:${APP_VERSION}_dev

# Install the chart from the local directory
helm upgrade kapparmor --install \
--atomic \
--debug \
--set image.tag=${APP_VERSION}_dev \
--set image.pullPolicy=Always \
--dry-run \
charts/kapparmor

echo
echo "> Is the previous result the expected one?"
echo "> Current K8S context:" "$(kubectl config current-context)"
read -r -p "> Are you sure? [Y/n] " response
if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then
helm upgrade kapparmor --install \
--atomic \
--timeout 120s \
--debug \
--set image.tag=${APP_VERSION}_dev \
--set image.pullPolicy=Always \
charts/kapparmor
else
echo " Bye."
echo
fi
43 changes: 43 additions & 0 deletions build/build-app.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/bin/bash
source ./config/config

# --- Validate App and Chart version
YML_CHART_VERSION="$(grep "version: [\"0-9\.]\+" charts/kapparmor/Chart.yaml |cut -d'"' -f2)"
YML_APP_VERSION="$(grep "appVersion: [\"0-9\.]\+" charts/kapparmor/Chart.yaml |cut -d'"' -f2)"

if [[ $APP_VERSION != $YML_APP_VERSION ]]; then
echo "The APP version declared in the Chart is different from the one in the config!"
exit 1
elif [[ $CHART_VERSION != $YML_CHART_VERSION ]]; then
echo "The APP version declared in the Chart is different from the one in the config!"
exit 1
fi

# Clean old images
echo "> Removing old and dangling old images..."
docker rmi "$(docker images --filter "reference=ghcr.io/tuxerrante/kapparmor" -q --no-trunc )"

# go build -o ./.go/bin ./...
# go test -v -coverprofile=coverage.out -covermode=atomic ./go/src/app/...
if [[ ! -f ".go/bin/golangci-lint" ]]; then
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b ./.go/bin
fi
echo "> Linting..."
./.go/bin/golangci-lint run

echo "> Scanning for suspicious constructs..."
go vet go/...

echo "> Creating test output..."
docker build --target test-coverage --tag "ghcr.io/tuxerrante/kapparmor:${APP_VERSION}_dev" .

#### To run it look into docs/testing.md
echo "> Building container image..."
docker build --tag "ghcr.io/tuxerrante/kapparmor:${APP_VERSION}_dev" \
--no-cache \
--build-arg POLL_TIME=30 \
--build-arg PROFILES_DIR=/app/profiles \
-f Dockerfile \
.

# docker run --rm -it --privileged --mount type=bind,source='/sys/kernel/security',target='/sys/kernel/security' --mount type=bind,source='/etc',target='/etc' --name kapparmor ghcr.io/tuxerrante/kapparmor:${APP_VERSION}_dev
5 changes: 3 additions & 2 deletions charts/kapparmor/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ type: application
home: https://artifacthub.io
kubeVersion: ">= 1.23.0-0"

version: "0.1.2"
appVersion: "0.1.2"
# Respect spaces and double quotes since this will be validated by the build-app script.
version: "0.1.5"
appVersion: "0.1.5"

keywords:
- kubernetes
Expand Down
5 changes: 5 additions & 0 deletions charts/kapparmor/templates/cm-profiles.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,10 @@ kind: ConfigMap
metadata:
name: kapparmor-profiles
namespace: {{ .Release.Namespace }}
labels:
{{- include "kapparmor.labels" . | nindent 4 }}
{{- with .Values.app.labels }}
{{- toYaml . | nindent 4 }}
{{- end }}
data:
{{ (.Files.Glob "profiles/*").AsConfig | indent 2 }}
5 changes: 5 additions & 0 deletions charts/kapparmor/templates/cm-settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ kind: ConfigMap
metadata:
name: kapparmor-settings
namespace: {{ .Release.Namespace }}
labels:
{{- include "kapparmor.labels" . | nindent 4 }}
{{- with .Values.app.labels }}
{{- toYaml . | nindent 4 }}
{{- end }}
data:
PROFILES_DIR: "{{ .Values.app.profiles_dir }}"
POLL_TIME: "{{ .Values.app.poll_time }}"
9 changes: 7 additions & 2 deletions charts/kapparmor/templates/daemonset.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ metadata:
name: {{ include "kapparmor.fullname" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "kapparmor.labels" . | nindent 4 }}
{{- include "kapparmor.labels" . | nindent 4 }}
{{- with .Values.daemonset.labels }}
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
selector:
matchLabels:
Expand All @@ -16,9 +19,11 @@ spec:
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}

labels:
{{- include "kapparmor.selectorLabels" . | nindent 8 }}
{{- with .Values.app.labels }}
{{- toYaml . | nindent 8 }}
{{- end }}

spec:
{{- with .Values.imagePullSecrets }}
Expand Down
3 changes: 3 additions & 0 deletions charts/kapparmor/templates/service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ metadata:
namespace: {{ .Release.Namespace }}
labels:
{{- include "kapparmor.labels" . | nindent 4 }}
{{- with .Values.app.labels }}
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
type: {{ .Values.service.type }}
ports:
Expand Down
5 changes: 5 additions & 0 deletions charts/kapparmor/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ fullnameOverride: ""
app:
profiles_dir: "/app/profiles"
poll_time: 60
labels:
# costgroup: "test"

serviceAccount:
# Specifies whether a service account should be created
Expand All @@ -22,6 +24,9 @@ serviceAccount:
# If not set and create is true, a name is generated using the fullname template
name: ""

daemonset:
labels: {}

podAnnotations: {}

podSecurityContext: {}
Expand Down
2 changes: 2 additions & 0 deletions config/config
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
APP_VERSION=0.1.5
CHART_VERSION=0.1.5
4 changes: 2 additions & 2 deletions cr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ chart-dirs:
- ./charts
chart-repos:
- kapparmor=https://tuxerrante.github.io/kapparmor/
helm-extra-args: --timeout 600s
helm-extra-args: --timeout 60s
validate-maintainers: true
generate-release-notes: true
make-release-latest: false
release-notes-file: CHANGELOG.md
release-notes-file: CHANGELOG.md
2 changes: 1 addition & 1 deletion ct.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ chart-dirs:
- ./charts
chart-repos:
- kapparmor=https://tuxerrante.github.io/kapparmor/
helm-extra-args: --timeout 600s
helm-extra-args: --timeout 60s
validate-maintainers: true
9 changes: 7 additions & 2 deletions docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,13 @@ docker run -it --network host --workdir=/data --volume ~/.kube/config:/root/.kub
/bin/sh -c "git config --global --add safe.directory /data; ct lint --print-config --charts ./charts/kapparmor"

# Replace here a commit id being part of an image tag
export GITHUB_SHA="sha-93d0dc4c597a8ae8a9febe1d68e674daf1fa919a"
helm install --dry-run --atomic --generate-name --timeout 30s --debug --set image.tag=$GITHUB_SHA charts/kapparmor/
export IMAGE_TAG="0.1.4_dev"
helm upgrade kapparmor --install --dry-run \
--atomic \
--timeout 30s \
--debug \
--namespace test \
--set image.tag=$IMAGE_TAG charts/kapparmor/

```

Expand Down
10 changes: 10 additions & 0 deletions go/src/app/filesystemOperations.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,16 @@ func IsProfileNameCorrect(directory, filename string) error {
}
scanner := bufio.NewScanner(fileReader)

// Validate the syntax
// the first index of a curly bracket should be greater than the first occurrence of "profile"
fileBytes, err := os.ReadFile(path.Join(directory, filename))
checkFatal(err)
profileIndex := bytes.Index(fileBytes, []byte("profile"))
curlyBracketIndex := bytes.Index(fileBytes, []byte("{"))
if curlyBracketIndex < 0 || curlyBracketIndex < profileIndex {
return errors.New("couldn't find a { after 'profile' keyword")
}

// Search for line starting with 'profile' word
for scanner.Scan() {
fileLine := scanner.Text()
Expand Down
Loading

0 comments on commit 5d23662

Please sign in to comment.