Skip to content

Commit

Permalink
Merge pull request #29 from jitsecurity/sc-27540-helm-chart-to-rotate…
Browse files Browse the repository at this point in the history
…-ecr-secrets

Add helm chart to rotate registry creds
  • Loading branch information
meshiyo authored Oct 21, 2024
2 parents 36ca8a8 + 98855ce commit 67bcc0d
Show file tree
Hide file tree
Showing 11 changed files with 401 additions and 0 deletions.
4 changes: 4 additions & 0 deletions src/kubernetes/jit_ecr/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
apiVersion: v2
name: jit_ecr
version: 0.1.0
description: Helm chart to manage registry credentials for JIT using authentication APIs
129 changes: 129 additions & 0 deletions src/kubernetes/jit_ecr/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# JIT Container Registry Credentials Manager

## Overview

This Helm chart deploys a system to manage and rotate Jit container registry secrets automatically. It ensures that containers (which are controls) can be pulled from the Jit Container Registry by maintaining up-to-date credentials.

## Purpose

The main purpose of this chart is to:

1. Authenticate with the Jit API to obtain container registry credentials.
2. Create and update a Kubernetes secret containing these credentials.
3. Periodically refresh the credentials to ensure continued access to the Jit Container Registry.

## Components

The chart consists of the following main components:

1. **Initial Login Job**: A one-time job that runs immediately after chart installation to set up the initial container registry credentials.
2. **Refresh CronJob**: A periodically running job that refreshes the container registry credentials to ensure they remain valid.
3. **ConfigMap**: Contains the script used by both the initial job and the CronJob to fetch and update the credentials.
4. **ServiceAccount and RBAC**: Provides necessary permissions for the jobs to create and update secrets in the specified namespace.

## Prerequisites

- Kubernetes 1.16+
- Helm 3.0+
- A valid Jit account with API credentials (Client ID and Secret)
- These can be obtained from https://docs.jit.io/docs/managing-users#generating-api-tokens
- The API credentials should be created with the Member role
- The secret should be kept securely and not shared or exposed publicly

## Installation

To install the chart with the release name `jit-registry`:

```bash
helm install jit-registry . \
--set client_id=your-client-id \
--set secret=your-secret \
--set namespace=your-namespace
```

## Configuration

The following table lists the configurable parameters of the JIT Container Registry Credentials Manager chart and their default values.

| Parameter | Description | Default |
|-----------|-------------|---------|
| `client_id` | Jit API Client ID (Member role) | `"<JIT_API_CLIENT_ID>"` |
| `secret` | Jit API Secret | `"<JIT_API_SECRET>"` |
| `jit_base_url` | Jit API Base URL | `"https://api.jit.io"` |
| `registry_name` | Jit Container Registry Name | `"registry.jit.io"` |
| `keep_job_history_seconds` | Time (in seconds) to keep job history | `86400` |
| `namespace` | Kubernetes namespace to deploy to | `"default"` |
| `jit_ecr_secret_name` | Name of the Kubernetes secret for container registry credentials | `"jit-registry-creds"` |

To modify any of these parameters, you can use the `--set key=value[,key=value]` argument to `helm install` or `helm upgrade`, or modify the `values.yaml` file directly.

Note: The `client_id` and `secret` should be obtained from https://docs.jit.io/docs/managing-users#generating-api-tokens. Make sure to use the "Member" role when generating these credentials. Please store these values in a secure place and never expose them publicly.

The `jit_ecr_secret_name` should match the Kubernetes runner configuration. For example, in GitLab:

```yaml
[runners.kubernetes]
poll_timeout = 2000
node_selector_overwrite_allowed = ".*"
image_pull_secrets=["jit-registry-creds"]
```

## Usage

After installation, the chart will:

1. Create an initial Kubernetes secret with container registry credentials.
2. Set up a CronJob to refresh these credentials periodically.

You can use the created secret (`jit-registry-creds` by default) in your pod specifications to pull images from the Jit Container Registry:

```yaml
spec:
imagePullSecrets:
- name: jit-registry-creds
containers:
- name: your-container
image: registry.jit.io/your-image:tag
```
## Monitoring and Troubleshooting
To check the status of the initial login job:
```bash
kubectl get jobs -n your-namespace jit-registry-initial-login
```

To check the status of the refresh CronJob:

```bash
kubectl get cronjobs -n your-namespace jit-registry-refresh
```

To view logs of the most recent job execution:

```bash
kubectl logs -n your-namespace job/jit-registry-refresh-<job-id>
```

For more detailed instructions, please refer to the NOTES.txt file that is displayed after chart installation.

## Uninstalling the Chart

To uninstall/delete the `jit-registry` deployment:

```bash
helm delete jit-registry
```

This command removes all the Kubernetes components associated with the chart and deletes the release.

## Security Considerations

- The Jit API Secret is sensitive information. Always handle it securely and avoid exposing it in logs, command-line arguments, or version control systems.
- Use Kubernetes Secrets or a secure secrets management system to store the `client_id` and `secret`.
- Regularly rotate your Jit API credentials as per your organization's security policies.

## Support

For any issues or questions, please contact Jit support or open an issue in the chart's repository.
28 changes: 28 additions & 0 deletions src/kubernetes/jit_ecr/templates/NOTES.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{{- if .Values.client_id }}
{{- if .Values.secret }}
Congratulations! You've installed the JIT registry credentials manager.

To verify that the initial login was successful, run the following command:

kubectl logs -n {{ .Values.namespace }} $(kubectl get pods -n {{ .Values.namespace }} -l job-name=jit-ecr-initial-login --sort-by=.metadata.creationTimestamp --output=jsonpath='{.items[-1:].metadata.name}')

This command will display the logs of the most recently created pod for the initial login job. Look for the message "registry credentials updated successfully on <date>" at the end of the logs.

If you don't see the success message or encounter any errors, you can describe the job for more information:
kubectl describe job jit-ecr-initial-login -n {{ .Values.namespace }}

The registry credentials secret has been created as {{ .Values.jit_ecr_secret_name }} in the {{ .Values.namespace }} namespace.

To verify the created secret, run:
kubectl get secret {{ .Values.jit_ecr_secret_name }} --namespace {{ .Values.namespace }}

A CronJob has also been set up to refresh these credentials periodically. You can check its status and logs with:
kubectl get cronjob -n {{ .Values.namespace }} jit-ecr-refresh
kubectl logs -n {{ .Values.namespace }} $(kubectl get pods -n {{ .Values.namespace }} -l cronjob-name=jit-ecr-refresh --sort-by=.metadata.creationTimestamp --output=jsonpath='{.items[-1:].metadata.name}')

{{- else }}
Error: "secret" value is mandatory.
{{- end }}
{{- else }}
Error: "client_id" value is mandatory.
{{- end }}
43 changes: 43 additions & 0 deletions src/kubernetes/jit_ecr/templates/cronjob.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{{- if .Values.client_id }}
{{- if .Values.secret }}
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: jit-ecr-refresh
namespace: {{ .Values.namespace }}
spec:
schedule: "0 */10 * * *"
successfulJobsHistoryLimit: 1
failedJobsHistoryLimit: 1
jobTemplate:
spec:
ttlSecondsAfterFinished: {{ .Values.keep_job_history_seconds }}
template:
spec:
serviceAccountName: sa-jit-ecr
containers:
- name: jit-ecr-refresh
image: alpine/k8s:1.26.8
command: ["/bin/sh", "/scripts/jit-ecr-script.sh"]
volumeMounts:
- name: secrets
mountPath: /secrets
readOnly: true
- name: script-volume
mountPath: /scripts
readOnly: true
resources:
limits:
cpu: 100m
memory: 128Mi
volumes:
- name: secrets
secret:
secretName: ecr-registry-helper-secrets
- name: script-volume
configMap:
name: jit-ecr-script
restartPolicy: OnFailure
{{- end }}
{{- end }}
13 changes: 13 additions & 0 deletions src/kubernetes/jit_ecr/templates/jit_creds_secret.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{{- if .Values.client_id }}
{{- if .Values.secret }}
---
apiVersion: v1
kind: Secret
metadata:
name: ecr-registry-helper-secrets
namespace: {{ .Values.namespace }}
stringData:
client_id: "{{ .Values.client_id }}"
secret: "{{ .Values.secret }}"
{{- end }}
{{- end }}
38 changes: 38 additions & 0 deletions src/kubernetes/jit_ecr/templates/job.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{{- if .Values.client_id }}
{{- if .Values.secret }}
---
apiVersion: batch/v1
kind: Job
metadata:
name: jit-ecr-initial-login
namespace: {{ .Values.namespace }}
spec:
ttlSecondsAfterFinished: {{ .Values.keep_job_history_seconds }}
template:
spec:
serviceAccountName: sa-jit-ecr
containers:
- name: jit-ecr-initial-login
image: alpine/k8s:1.26.8
command: ["/bin/sh", "/scripts/jit-ecr-script.sh"]
volumeMounts:
- name: secrets
mountPath: /secrets
readOnly: true
- name: script-volume
mountPath: /scripts
readOnly: true
resources:
limits:
cpu: 100m
memory: 128Mi
volumes:
- name: secrets
secret:
secretName: ecr-registry-helper-secrets
- name: script-volume
configMap:
name: jit-ecr-script
restartPolicy: OnFailure
{{- end }}
{{- end }}
17 changes: 17 additions & 0 deletions src/kubernetes/jit_ecr/templates/role.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: {{ .Values.namespace }}
name: role-access-to-jit-ecr-secret
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["delete"]
resourceNames: ["{{ .Values.jit_ecr_secret_name }}"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["create"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get"]
resourceNames: ["ecr-registry-helper-secrets"]
14 changes: 14 additions & 0 deletions src/kubernetes/jit_ecr/templates/rolebinding.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: jit-ecr-role-binding
namespace: {{ .Values.namespace }}
subjects:
- kind: ServiceAccount
name: sa-jit-ecr
namespace: {{ .Values.namespace }}
apiGroup: ""
roleRef:
kind: Role
name: role-access-to-jit-ecr-secret
apiGroup: ""
86 changes: 86 additions & 0 deletions src/kubernetes/jit_ecr/templates/script-configmap.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: jit-ecr-script
namespace: {{ .Values.namespace }}
data:
jit-ecr-script.sh: |
#!/bin/sh
set -e
# Install jq
apk add --no-cache jq
# Wait for the secret to be available
until kubectl get secret ecr-registry-helper-secrets -n {{ .Values.namespace }}; do
echo "Waiting for secret ecr-registry-helper-secrets to be created..."
sleep 5
done
# Read credentials from the secret
CLIENT_ID=$(kubectl get secret ecr-registry-helper-secrets -n {{ .Values.namespace }} -o jsonpath='{.data.client_id}' | base64 -d)
SECRET=$(kubectl get secret ecr-registry-helper-secrets -n {{ .Values.namespace }} -o jsonpath='{.data.secret}' | base64 -d)
# Perform login to get access token
echo "Requesting access token..."
AUTH_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST {{ .Values.jit_base_url }}/authentication/login \
-H "Content-Type: application/json" \
-d "{\"clientId\": \"$CLIENT_ID\", \"secret\": \"$SECRET\"}")
AUTH_STATUS=$(echo "$AUTH_RESPONSE" | tail -n1)
AUTH_BODY=$(echo "$AUTH_RESPONSE" | sed '$d')
if [ "$AUTH_STATUS" -ne 200 ]; then
echo "Failed to obtain access token. HTTP Status: $AUTH_STATUS"
echo "Response body: $AUTH_BODY"
exit 1
fi
ACCESS_TOKEN=$(echo "$AUTH_BODY" | jq -r '.accessToken')
if [ -z "$ACCESS_TOKEN" ] || [ "$ACCESS_TOKEN" = "null" ]; then
echo "Failed to extract access token from response"
echo "Response body: $AUTH_BODY"
exit 1
fi
echo "Access token obtained successfully"
# Use access token to get registry credentials
echo "Requesting registry token..."
REGISTRY_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST {{ .Values.jit_base_url }}/authentication/registry/login \
-H "Authorization: Bearer $ACCESS_TOKEN")
REGISTRY_STATUS=$(echo "$REGISTRY_RESPONSE" | tail -n1)
REGISTRY_BODY=$(echo "$REGISTRY_RESPONSE" | sed '$d')
if [ "$REGISTRY_STATUS" -ne 200 ]; then
echo "Failed to obtain registry token. HTTP Status: $REGISTRY_STATUS"
echo "Response body: $REGISTRY_BODY"
exit 1
fi
REGISTRY_TOKEN="$REGISTRY_BODY"
if [ -z "$REGISTRY_TOKEN" ]; then
echo "Failed to obtain registry token"
echo "Response body: $REGISTRY_BODY"
exit 1
fi
echo "registry token obtained successfully"
# Delete existing Kubernetes Docker registry secret (ignore if not found)
echo "Deleting existing Kubernetes secret (if any)..."
kubectl delete secret {{ .Values.jit_ecr_secret_name }} -n {{ .Values.namespace }} 2>/dev/null || true
# Create new Kubernetes Docker registry secret
echo "Creating new Kubernetes secret..."
kubectl create secret docker-registry {{ .Values.jit_ecr_secret_name }} \
--docker-server={{ .Values.registry_name }} \
--docker-username=AWS \
--docker-password="$REGISTRY_TOKEN" \
--namespace={{ .Values.namespace }}
# Get current date and time
CURRENT_DATE=$(date "+%Y-%m-%d %H:%M:%S")
echo "Registry credentials updated successfully on $CURRENT_DATE"
5 changes: 5 additions & 0 deletions src/kubernetes/jit_ecr/templates/serviceaccount.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: sa-jit-ecr
namespace: {{ .Values.namespace }}
Loading

0 comments on commit 67bcc0d

Please sign in to comment.