Skip to content

Commit

Permalink
First simple iteration
Browse files Browse the repository at this point in the history
  • Loading branch information
angelbarrera92 committed Jan 4, 2023
1 parent bf1545a commit 68e8f2e
Show file tree
Hide file tree
Showing 12 changed files with 438 additions and 3 deletions.
53 changes: 53 additions & 0 deletions .github/workflows/linter.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
#################################
#################################
## Super Linter GitHub Actions ##
#################################
#################################
name: Lint Code Base

#############################
# Start the job on all push #
#############################
on:
push:
branches-ignore: [master, main]
# Remove the line above to run when pushing to master
pull_request:
branches: [master, main]

###############
# Set the Job #
###############
jobs:
build:
# Name the Job
name: Lint Code Base
# Set the agent to run on
runs-on: ubuntu-latest

##################
# Load all steps #
##################
steps:
##########################
# Checkout the code base #
##########################
- name: Checkout Code
uses: actions/checkout@v3
with:
# Full git history is needed to get a proper
# list of changed files within `super-linter`
fetch-depth: 0

################################
# Run Linter against code base #
################################
- name: Lint Code Base
uses: github/super-linter@v4
env:
VALIDATE_ALL_CODEBASE: false
KUBERNETES_KUBEVAL_OPTIONS: --ignore-missing-schemas
VALIDATE_PYTHON_MYPY: false
DEFAULT_BRANCH: main
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
34 changes: 34 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
name: Release

on:
push:
tags:
- "*"

jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Build and publish the container image for ${{ github.repository }}:${{ github.ref_name }}
uses: macbre/push-to-ghcr@v12
with:
image_name: ${{ github.repository }}
github_token: ${{ secrets.GITHUB_TOKEN }}
dockerfile: build/container/Dockerfile
image_tag: ${{ github.ref_name }}
- name: Build and publish the container image for ${{ github.repository }}:latest
uses: macbre/push-to-ghcr@v12
with:
image_name: ${{ github.repository }}
github_token: ${{ secrets.GITHUB_TOKEN }}
dockerfile: build/container/Dockerfile
image_tag: latest
- name: Create a Release
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
with:
name: X K8Spin internal kubeconfig generator ${{ github.ref_name }}
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ __pycache__/

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
Expand Down Expand Up @@ -127,3 +126,5 @@ dmypy.json

# Pyre type checker
.pyre/

.vscode
12 changes: 12 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
IMAGE=internal-kubeconfig-generator

lint:
@docker run --rm -e RUN_LOCAL=true -e KUBERNETES_KUBEVAL_OPTIONS=--ignore-missing-schemas -e VALIDATE_PYTHON_MYPY=false -v $(shell pwd):/tmp/lint github/super-linter:v4

build-local:
@docker build --no-cache --pull -t $(IMAGE):local . -f build/container/Dockerfile

clean:
@find . -name "*.pyc" -exec rm -f {} \;
@find . -name "__pycache__" -exec rm -rf {} \;
@rm -rf super-linter.log
107 changes: 105 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,105 @@
# internal-kubeconfig-generator
Create dynamically a kubeconfig inside a secret with a kubeconfig from a serviceaccount
# Internal kubeconfig generator

This simple controller generates a `kubeconfig` and stores it in a `Secret` for each
[`Secret` created in a specific namespace for a specific service account.](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#manually-create-a-long-lived-api-token-for-a-serviceaccount)

## Warning

**The project is currently under development and is not ready for production use.**

## Motivation

The main motivation for developing this controller is to enable the multi-tenancy feature of Crossplane.

The crossplane kubernetes provider does not support the usage of `ServiceAccount`s resource yet to configure
a `providerConfig`. Then, the only way to configure a `providerConfig` is to use a `Secret` with a `kubeconfig`.

This is where this controller comes in.

## How it works

The controller watches for `Secret's` with a few specific annotations:

- `kubernetes.io/service-account.name`: This is the required annotation to tell the kubernetes controller which `ServiceAccount` belongs to this `Secret`.
- `x.k8spin.cloud/kubeconfig`: This is the trigger annotation to tell the controller to generate a `kubeconfig` for this `Secret`.

The controller will generate a new `Secret` with the same name as the `ServiceAccount` defined in the `kubernetes.io/service-account.name` annotation ending with `-kubeconfig` suffix.

## How to use it

```bash
$ kubectl apply -f https://raw.githubusercontent.com/angelbarrera92/internal-kubeconfig-generator/master/deploy/kubernetes/deploy.yaml
namespace/k8spin-system created
serviceaccount/internal-kubeconfig-generator created
clusterrole.rbac.authorization.k8s.io/internal-kubeconfig-generator created
clusterrolebinding.rbac.authorization.k8s.io/internal-kubeconfig-generator created
deployment.apps/internal-kubeconfig-generator created
$ kubectl wait --for=condition=available --timeout=600s deployment/internal-kubeconfig-generator -n k8spin-system
deployment.apps/internal-kubeconfig-generator condition met
```

### Demo

```bash
$ kubectl apply -f https://raw.githubusercontent.com/angelbarrera92/internal-kubeconfig-generator/master/hack/demo.yaml
clusterrole.rbac.authorization.k8s.io/provider-kubernetes-view created
clusterrolebinding.rbac.authorization.k8s.io/provider-kubernetes-view created
serviceaccount/provider-kubernetes-view created
secret/provider-kubernetes-view created
```

This creates a set of resources:
- A `ServiceAccount` named `provider-kubernetes-view`
- [The `Secret` named `provider-kubernetes-view` that contains the `token` for the `ServiceAccount`.](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#manually-create-a-long-lived-api-token-for-a-serviceaccount)
- `ClusterRole` and `ClusterRoleBinding` to allow the `ServiceAccount` to list the `Namespaces`.

Then, as the `secret` named `provider-kubernetes-view` has the `x.k8spin.cloud/kubeconfig` annotation, the controller will generate a new `Secret` named `provider-kubernetes-view-kubeconfig` with the `kubeconfig` for the `ServiceAccount`.

```bash
$ kubectl get secret/provider-kubernetes-view-kubeconfig -o yaml
apiVersion: v1
data:
kubeconfig: <REDACTED>
kind: Secret
metadata:
creationTimestamp: "2023-01-04T15:01:39Z"
name: provider-kubernetes-view-kubeconfig
namespace: default
ownerReferences:
- apiVersion: v1
kind: Secret
name: provider-kubernetes-view
uid: fe338cb5-ac4e-4c7f-9e5a-6b7c68216145
resourceVersion: "603"
uid: 6c853730-ca5c-4e7d-bfdf-912e31b9c4ec
type: Opaque
```

#### Test

Includes a `Job` that uses the generated `kubeconfig` to list the `Namespaces` in the cluster.

```bash
$ kubectl apply -f https://raw.githubusercontent.com/angelbarrera92/internal-kubeconfig-generator/master/hack/demo-test.yaml
job.batch/list-namespaces created
$ kubectl logs -f job/list-namespaces
NAME STATUS AGE
default Active 8m17s
kube-system Active 8m17s
kube-public Active 8m17s
kube-node-lease Active 8m16s
k8spin-system Active 7m43s
```

## Development

### Prerequisites

- [python3](https://www.python.org/downloads/)
- [virtualenv](https://virtualenv.pypa.io/en/latest/installation.html)
- [docker](https://docs.docker.com/install/)


## License

[MIT](LICENSE)
16 changes: 16 additions & 0 deletions build/container/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# hadolint ignore=DL3007
FROM cgr.dev/chainguard/python:latest

# Set the working directory to /home/nonroot
WORKDIR /home/nonroot

# Setup the virtual environment
RUN ["/usr/bin/python3", "-m" , "venv", "--upgrade-deps", ".venv"]
COPY requirements.txt requirements.txt
RUN [".venv/bin/pip", "install", "--disable-pip-version-check", "-r", "requirements.txt"]

# Copy the application
COPY main.py main.py

# Run the application
ENTRYPOINT [".venv/bin/kopf", "run", "-A", "main.py"]
74 changes: 74 additions & 0 deletions deploy/kubernetes/deploy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: k8spin-system
labels:
app: k8spin.cloud
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: internal-kubeconfig-generator
namespace: k8spin-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: internal-kubeconfig-generator
rules:
# Allow to get, list and watch namespaces
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["get", "list", "watch"]
# Allow to get, list and watch secrets
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
# Allow to get, list and watch service accounts
- apiGroups: [""]
resources: ["serviceaccounts"]
verbs: ["get", "list", "watch"]
# Allow to get, list and watch customresourcedefinitions
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch"]
# Allow to post events
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: internal-kubeconfig-generator
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: internal-kubeconfig-generator
subjects:
- kind: ServiceAccount
name: internal-kubeconfig-generator
namespace: k8spin-system
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: k8spin.cloud
name: internal-kubeconfig-generator
namespace: k8spin-system
spec:
selector:
matchLabels:
app: k8spin.cloud
template:
metadata:
labels:
app: k8spin.cloud
spec:
serviceAccountName: internal-kubeconfig-generator
containers:
- name: internal-kubeconfig-generator
image: ghcr.io/angelbarrera92/internal-kubeconfig-generator:latest
imagePullPolicy: Always
28 changes: 28 additions & 0 deletions hack/demo-test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Create a Kubernetes job that runs a kubectl command to list all namespaces
# Use the kubeconfig that is inside the secret provider-kubernetes-view-kubeconfig in the kubeconfig key
---
apiVersion: batch/v1
kind: Job
metadata:
name: list-namespaces
namespace: default
spec:
template:
spec:
containers:
- name: kubectl
image: bitnami/kubectl:1.25
command: ["kubectl", "get", "ns", "--kubeconfig", "/kubeconfig/config"]
volumeMounts:
- name: kubeconfig
mountPath: /kubeconfig
readOnly: true
volumes:
- name: kubeconfig
secret:
secretName: provider-kubernetes-view-kubeconfig
items:
- key: kubeconfig
path: config
restartPolicy: Never
backoffLimit: 4
40 changes: 40 additions & 0 deletions hack/demo.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
# Create a clusterrole that allows to list namespaces
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: provider-kubernetes-view
rules:
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["get", "list", "watch"]
---
# Create a clusterrolebinding that binds the clusterrole to the serviceaccount provider-kubernetes-view
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: provider-kubernetes-view
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: provider-kubernetes-view
subjects:
- kind: ServiceAccount
name: provider-kubernetes-view
namespace: default
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: provider-kubernetes-view
namespace: default
---
apiVersion: v1
kind: Secret
metadata:
name: provider-kubernetes-view
namespace: default
annotations:
kubernetes.io/service-account.name: provider-kubernetes-view
x.k8spin.cloud/kubeconfig: ""
type: kubernetes.io/service-account-token
Loading

0 comments on commit 68e8f2e

Please sign in to comment.