Skip to content

Commit

Permalink
# This is a combination of 20 commits.
Browse files Browse the repository at this point in the history
# This is the 1st commit message:

functional, permissions need to be modified

# This is the commit message #2:

bug fixes -- passing testing

# This is the commit message #3:

add README

# This is the commit message #4:

fix readme

# This is the commit message #5:

fix readme

# This is the commit message #6:

disable sync interval override

# This is the commit message #7:

add prerequisites to readme

# This is the commit message #8:

add more detail on create.sh compatibility

# This is the commit message #9:

add to create.sh to detect OS

# This is the commit message #10:

fix readme

# This is the commit message #11:

exit create.sh on any errors

# This is the commit message #12:

fix readme

# This is the commit message #13:

fix readme

# This is the commit message #14:

fix readme

# This is the commit message #15:

fix readme

# This is the commit message #16:

deletion works successfully

# This is the commit message #17:

fix readme

# This is the commit message #18:

fix readme

# This is the commit message #19:

fix readme

# This is the commit message #20:

fix readme
  • Loading branch information
Shubham Sakhuja authored and morgante committed Mar 11, 2019
1 parent ee462dd commit 808f586
Show file tree
Hide file tree
Showing 9 changed files with 245 additions and 0 deletions.
9 changes: 9 additions & 0 deletions tools/k8s-secret-syncer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM centos:7

COPY yum.repo /etc/yum.repos.d/

RUN yum install -y kubectl

COPY resource-syncer.py /

CMD python -u /resource-syncer.py
61 changes: 61 additions & 0 deletions tools/k8s-secret-syncer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# k8s-secret-syncer

## Overview

In k8s, Secrets are only available to resources in the same namespace as the Secret itself.
This means that if multiple namespaces need to use the same Secret, the only workaround is
to maintain a copy of the Secret in each namespace that requires it.

The *secret-syncer* tool is deployed into a k8s cluster to watch for any Secrets
created in a given namespace and copy them to specified target namespaces, allowing
one to maintain only a single copy of each Secret.

## Installation

#### Prerequisites
- You have a GCP project with a k8s cluster that you can deploy resources to, as well as `gcloud` and `kubectl` configured to point to each, respectively.
- Your docker client is [authenticated with GCR](https://cloud.google.com/sdk/gcloud/reference/auth/configure-docker).
- Your k8s user has permissions to [create RBAC resources](https://cloud.google.com/kubernetes-engine/docs/how-to/role-based-access-control#prerequisites_for_using_role-based_access_control).

#### Install *secret-syncer*
To install *secret-syncer*, run the below script from this directory:
```bash
./create.sh
```
This script uses `gcloud` and `kubectl` to build/push the container image for *secret-syncer*
and create a Deployment `secret-syncer`, along with necessary RBAC resources, to the
namespace `secrets` in the currently active k8s cluster.

## Configuration

*secret-syncer* can be configured with the below environment variables.

Env Var | Description | Default
--- | --- | ---
SOURCE_NS | The namespace to copy secrets from | secrets
SOURCE_ANNO | The annotation key to look for on secrets to determine the namespaces to it copy it to -- a secret will be copied to all namespaces that (regex) match its value for this annotation | ns-propagate
NS_BLACKLIST | A comma-separated list of namespaces to ignore as destinations for copying -- note that SOURCE_NS is automatically appended to this| kube-system,kube-public,default
SYNC_INTERVAL_SECONDS | The interval at which to look for and copy secrets | 300
SKIP_DELETE | By default, *secret-syncer* will delete secrets in any non-blacklisted namespaces that have the SOURCE_ANNO annotation but do not exist in the SOURCE_NS namespace -- set this to 'yes' to disable deletion | no
RESOURCE_KIND | The kind of resources to copy (change at your own risk) | secret

## Example Secret

Below is an example secret that would be copied to all namespaces (excluding those in `NS_BLACKLIST`).

```yaml
apiVersion: v1
kind: Secret
metadata:
name: my-registry-credentials
namespace: secrets
annotations:
ns-propagate: .*
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: [REDACTED]
```
## Notes
- Secret annotations can be modified via `kubectl edit`.
24 changes: 24 additions & 0 deletions tools/k8s-secret-syncer/create.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/bash

set -o errexit

function replace_image() {
local FILE=$1
local IMAGE=$2
[ "$(uname -a | cut -d ' ' -f 1)" == "Darwin" ] && BK='.bk' || BK='' # adjust for OS
sed -i $BK "s~^\(.* image:\).*$~\1 $IMAGE~g" $FILE
}

IMAGE=gcr.io/$(gcloud config get-value project)/resource-syncer:0.1
docker build -t $IMAGE . && docker push $IMAGE

# update image
replace_image manifests/deployment.yaml $IMAGE

# create ns, if DNE
NS=secrets
kubectl get ns $NS || kubectl create ns $NS

# create resources
kubectl apply -f manifests/rbac # create service account first
kubectl apply -f manifests
31 changes: 31 additions & 0 deletions tools/k8s-secret-syncer/manifests/deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: secret-syncer
namespace: secrets
spec:
replicas: 1
selector:
matchLabels:
app: secret-syncer
template:
metadata:
name: secret-syncer
labels:
app: secret-syncer
spec:
serviceAccountName: secret-syncer
containers:
- name: secret-syncer
image: gcr.io/my-cool-gcp-project/resource-syncer:0.1
imagePullPolicy: Always
#env:
#- name: SYNC_INTERVAL_SECONDS
# value: 10
resources:
requests:
cpu: 0.1
memory: 128M
limits:
cpu: 0.2
memory: 256M
13 changes: 13 additions & 0 deletions tools/k8s-secret-syncer/manifests/rbac/clusterrole.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: secret-syncer
rules:
# read namespaces
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["get", "list"]
# create/update secrets
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "create", "update", "patch", "delete"]
13 changes: 13 additions & 0 deletions tools/k8s-secret-syncer/manifests/rbac/rolebinding.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: secret-syncer
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: secret-syncer
subjects:
- kind: ServiceAccount
name: secret-syncer
namespace: secrets

5 changes: 5 additions & 0 deletions tools/k8s-secret-syncer/manifests/rbac/sa.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: secret-syncer
namespace: secrets
82 changes: 82 additions & 0 deletions tools/k8s-secret-syncer/resource-syncer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import os
import sys
import json
import subprocess
import re
import time
import traceback

# configuration
RESOURCE_KIND = os.environ.get('RESOURCE_KIND', 'secret') # cluster role must be granted accordingly
SYNC_INTERVAL_SECONDS = int(os.environ.get('SYNC_INTERVAL_SECONDS', 300))
SOURCE_NS = os.environ.get('SOURCE_NS', 'secrets')
SOURCE_ANNO = os.environ.get('SOURCE_ANNO', 'ns-propagate')
NS_BLACKLIST = os.environ.get('NS_BLACKLIST', 'kube-system,kube-public,default').split(',')
SKIP_DELETE = os.environ.get('SKIP_DELETE', 'no') == 'yes'
NS_BLACKLIST.append(SOURCE_NS) # don't copy back to source

def kube_get(kind, namespace=None, name=None):
cmd = 'kubectl get -o json ' + kind + (' -n ' + namespace if namespace else ' ') + (name if name else ' ')
resp = subprocess.check_output(['/bin/sh', '-c', cmd])
return json.loads(resp)['items']

def kube_apply(definition):
return subprocess.call(["/bin/sh", "-c", "echo '" + json.dumps(definition) + "' | kubectl apply -f -"])

def kube_delete(kind, namespace, name):
print 'deleting %s %s in namespace %s' % (kind, name, namespace)
return subprocess.call(["/bin/sh", "-c", "kubectl delete %s %s -n %s" % (kind, name, namespace)])

# modify a resource to strip out unique fields and switch the namespace
def kube_switch_ns(definition, target_ns):
definition['metadata']['namespace'] = target_ns
for f in ('creationTimestamp', 'resourceVersion', 'uid'):
if f in definition['metadata']:
del definition['metadata'][f]

# get all namespaces
def kube_get_ns():
return [ns for ns in kube_get('namespace') if ns['metadata']['name'] not in NS_BLACKLIST]

# get resources from source namespace
def get_resources(kind, ns):
resources = kube_get(kind, ns)
return [r for r in resources if SOURCE_ANNO in r['metadata'].get('annotations', {})]

# create resources in target namespaces
def sync_resources(source_resources):
namespaces = kube_get_ns()
# create/update
for r in source_resources:
target_ns_pattern = r['metadata']['annotations'][SOURCE_ANNO]
target_ns_list = [ns for ns in namespaces if re.match('^' + target_ns_pattern + '$', ns['metadata']['name'])]
for ns in target_ns_list:
print 'copying %s %s to namespace %s' % (RESOURCE_KIND, r['metadata']['name'], ns['metadata']['name'])
kube_switch_ns(r, ns['metadata']['name'])
if kube_apply(r) != 0:
raise ValueError('error in copying ' + RESOURCE_KIND)
# delete
if not SKIP_DELETE:
source_resources_list = [r['metadata']['name'] for r in source_resources]
for ns in namespaces:
current_resources_list = [r['metadata']['name'] for r in get_resources(RESOURCE_KIND, ns['metadata']['name'])]
[kube_delete(RESOURCE_KIND, ns['metadata']['name'], r_name) for r_name in current_resources_list if r_name not in source_resources_list]

# main
print '''
starting resource syncer with the following properties:
- syncing from source namespace "%s"
- syncing resources of kind "%s" that include the annotation "%s"
- omitting the following namespaces as sync destinations: "%s"
''' % (SOURCE_NS, RESOURCE_KIND, SOURCE_ANNO, NS_BLACKLIST)

while True:
try:
print '====== checking namespace %s for %ss to sync ======' % (SOURCE_NS, RESOURCE_KIND)
resources = get_resources(RESOURCE_KIND, SOURCE_NS)
sync_resources(resources)
except Exception as e:
traceback.print_exc()
finally:
time.sleep(SYNC_INTERVAL_SECONDS)

7 changes: 7 additions & 0 deletions tools/k8s-secret-syncer/yum.repo
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg

0 comments on commit 808f586

Please sign in to comment.