Skip to content

Commit 67bcc0d

Browse files
authored
Merge pull request #29 from jitsecurity/sc-27540-helm-chart-to-rotate-ecr-secrets
Add helm chart to rotate registry creds
2 parents 36ca8a8 + 98855ce commit 67bcc0d

11 files changed

Lines changed: 401 additions & 0 deletions

File tree

src/kubernetes/jit_ecr/Chart.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
apiVersion: v2
2+
name: jit_ecr
3+
version: 0.1.0
4+
description: Helm chart to manage registry credentials for JIT using authentication APIs

src/kubernetes/jit_ecr/README.md

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# JIT Container Registry Credentials Manager
2+
3+
## Overview
4+
5+
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.
6+
7+
## Purpose
8+
9+
The main purpose of this chart is to:
10+
11+
1. Authenticate with the Jit API to obtain container registry credentials.
12+
2. Create and update a Kubernetes secret containing these credentials.
13+
3. Periodically refresh the credentials to ensure continued access to the Jit Container Registry.
14+
15+
## Components
16+
17+
The chart consists of the following main components:
18+
19+
1. **Initial Login Job**: A one-time job that runs immediately after chart installation to set up the initial container registry credentials.
20+
2. **Refresh CronJob**: A periodically running job that refreshes the container registry credentials to ensure they remain valid.
21+
3. **ConfigMap**: Contains the script used by both the initial job and the CronJob to fetch and update the credentials.
22+
4. **ServiceAccount and RBAC**: Provides necessary permissions for the jobs to create and update secrets in the specified namespace.
23+
24+
## Prerequisites
25+
26+
- Kubernetes 1.16+
27+
- Helm 3.0+
28+
- A valid Jit account with API credentials (Client ID and Secret)
29+
- These can be obtained from https://docs.jit.io/docs/managing-users#generating-api-tokens
30+
- The API credentials should be created with the Member role
31+
- The secret should be kept securely and not shared or exposed publicly
32+
33+
## Installation
34+
35+
To install the chart with the release name `jit-registry`:
36+
37+
```bash
38+
helm install jit-registry . \
39+
--set client_id=your-client-id \
40+
--set secret=your-secret \
41+
--set namespace=your-namespace
42+
```
43+
44+
## Configuration
45+
46+
The following table lists the configurable parameters of the JIT Container Registry Credentials Manager chart and their default values.
47+
48+
| Parameter | Description | Default |
49+
|-----------|-------------|---------|
50+
| `client_id` | Jit API Client ID (Member role) | `"<JIT_API_CLIENT_ID>"` |
51+
| `secret` | Jit API Secret | `"<JIT_API_SECRET>"` |
52+
| `jit_base_url` | Jit API Base URL | `"https://api.jit.io"` |
53+
| `registry_name` | Jit Container Registry Name | `"registry.jit.io"` |
54+
| `keep_job_history_seconds` | Time (in seconds) to keep job history | `86400` |
55+
| `namespace` | Kubernetes namespace to deploy to | `"default"` |
56+
| `jit_ecr_secret_name` | Name of the Kubernetes secret for container registry credentials | `"jit-registry-creds"` |
57+
58+
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.
59+
60+
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.
61+
62+
The `jit_ecr_secret_name` should match the Kubernetes runner configuration. For example, in GitLab:
63+
64+
```yaml
65+
[runners.kubernetes]
66+
poll_timeout = 2000
67+
node_selector_overwrite_allowed = ".*"
68+
image_pull_secrets=["jit-registry-creds"]
69+
```
70+
71+
## Usage
72+
73+
After installation, the chart will:
74+
75+
1. Create an initial Kubernetes secret with container registry credentials.
76+
2. Set up a CronJob to refresh these credentials periodically.
77+
78+
You can use the created secret (`jit-registry-creds` by default) in your pod specifications to pull images from the Jit Container Registry:
79+
80+
```yaml
81+
spec:
82+
imagePullSecrets:
83+
- name: jit-registry-creds
84+
containers:
85+
- name: your-container
86+
image: registry.jit.io/your-image:tag
87+
```
88+
89+
## Monitoring and Troubleshooting
90+
91+
To check the status of the initial login job:
92+
93+
```bash
94+
kubectl get jobs -n your-namespace jit-registry-initial-login
95+
```
96+
97+
To check the status of the refresh CronJob:
98+
99+
```bash
100+
kubectl get cronjobs -n your-namespace jit-registry-refresh
101+
```
102+
103+
To view logs of the most recent job execution:
104+
105+
```bash
106+
kubectl logs -n your-namespace job/jit-registry-refresh-<job-id>
107+
```
108+
109+
For more detailed instructions, please refer to the NOTES.txt file that is displayed after chart installation.
110+
111+
## Uninstalling the Chart
112+
113+
To uninstall/delete the `jit-registry` deployment:
114+
115+
```bash
116+
helm delete jit-registry
117+
```
118+
119+
This command removes all the Kubernetes components associated with the chart and deletes the release.
120+
121+
## Security Considerations
122+
123+
- The Jit API Secret is sensitive information. Always handle it securely and avoid exposing it in logs, command-line arguments, or version control systems.
124+
- Use Kubernetes Secrets or a secure secrets management system to store the `client_id` and `secret`.
125+
- Regularly rotate your Jit API credentials as per your organization's security policies.
126+
127+
## Support
128+
129+
For any issues or questions, please contact Jit support or open an issue in the chart's repository.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{{- if .Values.client_id }}
2+
{{- if .Values.secret }}
3+
Congratulations! You've installed the JIT registry credentials manager.
4+
5+
To verify that the initial login was successful, run the following command:
6+
7+
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}')
8+
9+
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.
10+
11+
If you don't see the success message or encounter any errors, you can describe the job for more information:
12+
kubectl describe job jit-ecr-initial-login -n {{ .Values.namespace }}
13+
14+
The registry credentials secret has been created as {{ .Values.jit_ecr_secret_name }} in the {{ .Values.namespace }} namespace.
15+
16+
To verify the created secret, run:
17+
kubectl get secret {{ .Values.jit_ecr_secret_name }} --namespace {{ .Values.namespace }}
18+
19+
A CronJob has also been set up to refresh these credentials periodically. You can check its status and logs with:
20+
kubectl get cronjob -n {{ .Values.namespace }} jit-ecr-refresh
21+
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}')
22+
23+
{{- else }}
24+
Error: "secret" value is mandatory.
25+
{{- end }}
26+
{{- else }}
27+
Error: "client_id" value is mandatory.
28+
{{- end }}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{{- if .Values.client_id }}
2+
{{- if .Values.secret }}
3+
---
4+
apiVersion: batch/v1
5+
kind: CronJob
6+
metadata:
7+
name: jit-ecr-refresh
8+
namespace: {{ .Values.namespace }}
9+
spec:
10+
schedule: "0 */10 * * *"
11+
successfulJobsHistoryLimit: 1
12+
failedJobsHistoryLimit: 1
13+
jobTemplate:
14+
spec:
15+
ttlSecondsAfterFinished: {{ .Values.keep_job_history_seconds }}
16+
template:
17+
spec:
18+
serviceAccountName: sa-jit-ecr
19+
containers:
20+
- name: jit-ecr-refresh
21+
image: alpine/k8s:1.26.8
22+
command: ["/bin/sh", "/scripts/jit-ecr-script.sh"]
23+
volumeMounts:
24+
- name: secrets
25+
mountPath: /secrets
26+
readOnly: true
27+
- name: script-volume
28+
mountPath: /scripts
29+
readOnly: true
30+
resources:
31+
limits:
32+
cpu: 100m
33+
memory: 128Mi
34+
volumes:
35+
- name: secrets
36+
secret:
37+
secretName: ecr-registry-helper-secrets
38+
- name: script-volume
39+
configMap:
40+
name: jit-ecr-script
41+
restartPolicy: OnFailure
42+
{{- end }}
43+
{{- end }}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{{- if .Values.client_id }}
2+
{{- if .Values.secret }}
3+
---
4+
apiVersion: v1
5+
kind: Secret
6+
metadata:
7+
name: ecr-registry-helper-secrets
8+
namespace: {{ .Values.namespace }}
9+
stringData:
10+
client_id: "{{ .Values.client_id }}"
11+
secret: "{{ .Values.secret }}"
12+
{{- end }}
13+
{{- end }}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{{- if .Values.client_id }}
2+
{{- if .Values.secret }}
3+
---
4+
apiVersion: batch/v1
5+
kind: Job
6+
metadata:
7+
name: jit-ecr-initial-login
8+
namespace: {{ .Values.namespace }}
9+
spec:
10+
ttlSecondsAfterFinished: {{ .Values.keep_job_history_seconds }}
11+
template:
12+
spec:
13+
serviceAccountName: sa-jit-ecr
14+
containers:
15+
- name: jit-ecr-initial-login
16+
image: alpine/k8s:1.26.8
17+
command: ["/bin/sh", "/scripts/jit-ecr-script.sh"]
18+
volumeMounts:
19+
- name: secrets
20+
mountPath: /secrets
21+
readOnly: true
22+
- name: script-volume
23+
mountPath: /scripts
24+
readOnly: true
25+
resources:
26+
limits:
27+
cpu: 100m
28+
memory: 128Mi
29+
volumes:
30+
- name: secrets
31+
secret:
32+
secretName: ecr-registry-helper-secrets
33+
- name: script-volume
34+
configMap:
35+
name: jit-ecr-script
36+
restartPolicy: OnFailure
37+
{{- end }}
38+
{{- end }}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
apiVersion: rbac.authorization.k8s.io/v1
2+
kind: Role
3+
metadata:
4+
namespace: {{ .Values.namespace }}
5+
name: role-access-to-jit-ecr-secret
6+
rules:
7+
- apiGroups: [""]
8+
resources: ["secrets"]
9+
verbs: ["delete"]
10+
resourceNames: ["{{ .Values.jit_ecr_secret_name }}"]
11+
- apiGroups: [""]
12+
resources: ["secrets"]
13+
verbs: ["create"]
14+
- apiGroups: [""]
15+
resources: ["secrets"]
16+
verbs: ["get"]
17+
resourceNames: ["ecr-registry-helper-secrets"]
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
kind: RoleBinding
2+
apiVersion: rbac.authorization.k8s.io/v1
3+
metadata:
4+
name: jit-ecr-role-binding
5+
namespace: {{ .Values.namespace }}
6+
subjects:
7+
- kind: ServiceAccount
8+
name: sa-jit-ecr
9+
namespace: {{ .Values.namespace }}
10+
apiGroup: ""
11+
roleRef:
12+
kind: Role
13+
name: role-access-to-jit-ecr-secret
14+
apiGroup: ""
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
apiVersion: v1
2+
kind: ConfigMap
3+
metadata:
4+
name: jit-ecr-script
5+
namespace: {{ .Values.namespace }}
6+
data:
7+
jit-ecr-script.sh: |
8+
#!/bin/sh
9+
set -e
10+
11+
# Install jq
12+
apk add --no-cache jq
13+
14+
# Wait for the secret to be available
15+
until kubectl get secret ecr-registry-helper-secrets -n {{ .Values.namespace }}; do
16+
echo "Waiting for secret ecr-registry-helper-secrets to be created..."
17+
sleep 5
18+
done
19+
20+
# Read credentials from the secret
21+
CLIENT_ID=$(kubectl get secret ecr-registry-helper-secrets -n {{ .Values.namespace }} -o jsonpath='{.data.client_id}' | base64 -d)
22+
SECRET=$(kubectl get secret ecr-registry-helper-secrets -n {{ .Values.namespace }} -o jsonpath='{.data.secret}' | base64 -d)
23+
24+
# Perform login to get access token
25+
echo "Requesting access token..."
26+
AUTH_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST {{ .Values.jit_base_url }}/authentication/login \
27+
-H "Content-Type: application/json" \
28+
-d "{\"clientId\": \"$CLIENT_ID\", \"secret\": \"$SECRET\"}")
29+
AUTH_STATUS=$(echo "$AUTH_RESPONSE" | tail -n1)
30+
AUTH_BODY=$(echo "$AUTH_RESPONSE" | sed '$d')
31+
32+
if [ "$AUTH_STATUS" -ne 200 ]; then
33+
echo "Failed to obtain access token. HTTP Status: $AUTH_STATUS"
34+
echo "Response body: $AUTH_BODY"
35+
exit 1
36+
fi
37+
38+
ACCESS_TOKEN=$(echo "$AUTH_BODY" | jq -r '.accessToken')
39+
40+
if [ -z "$ACCESS_TOKEN" ] || [ "$ACCESS_TOKEN" = "null" ]; then
41+
echo "Failed to extract access token from response"
42+
echo "Response body: $AUTH_BODY"
43+
exit 1
44+
fi
45+
46+
echo "Access token obtained successfully"
47+
48+
# Use access token to get registry credentials
49+
echo "Requesting registry token..."
50+
REGISTRY_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST {{ .Values.jit_base_url }}/authentication/registry/login \
51+
-H "Authorization: Bearer $ACCESS_TOKEN")
52+
REGISTRY_STATUS=$(echo "$REGISTRY_RESPONSE" | tail -n1)
53+
REGISTRY_BODY=$(echo "$REGISTRY_RESPONSE" | sed '$d')
54+
55+
if [ "$REGISTRY_STATUS" -ne 200 ]; then
56+
echo "Failed to obtain registry token. HTTP Status: $REGISTRY_STATUS"
57+
echo "Response body: $REGISTRY_BODY"
58+
exit 1
59+
fi
60+
61+
REGISTRY_TOKEN="$REGISTRY_BODY"
62+
63+
if [ -z "$REGISTRY_TOKEN" ]; then
64+
echo "Failed to obtain registry token"
65+
echo "Response body: $REGISTRY_BODY"
66+
exit 1
67+
fi
68+
69+
echo "registry token obtained successfully"
70+
71+
# Delete existing Kubernetes Docker registry secret (ignore if not found)
72+
echo "Deleting existing Kubernetes secret (if any)..."
73+
kubectl delete secret {{ .Values.jit_ecr_secret_name }} -n {{ .Values.namespace }} 2>/dev/null || true
74+
75+
# Create new Kubernetes Docker registry secret
76+
echo "Creating new Kubernetes secret..."
77+
kubectl create secret docker-registry {{ .Values.jit_ecr_secret_name }} \
78+
--docker-server={{ .Values.registry_name }} \
79+
--docker-username=AWS \
80+
--docker-password="$REGISTRY_TOKEN" \
81+
--namespace={{ .Values.namespace }}
82+
83+
# Get current date and time
84+
CURRENT_DATE=$(date "+%Y-%m-%d %H:%M:%S")
85+
86+
echo "Registry credentials updated successfully on $CURRENT_DATE"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
apiVersion: v1
2+
kind: ServiceAccount
3+
metadata:
4+
name: sa-jit-ecr
5+
namespace: {{ .Values.namespace }}

0 commit comments

Comments
 (0)