Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add helm chart to rotate registry creds #29

Merged
merged 4 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security control: Iac Misconfig Detection Kubernetes

Type: Applications Credentials In Configuration Files

Description: More details in the link below

Severity: HIGH

Learn more about this issue


Jit Bot commands and options (e.g., ignore issue)

You can trigger Jit actions by commenting on this PR review:

  • #jit_ignore_fp Ignore and mark this specific single instance of finding as “False Positive”
  • #jit_ignore_accept Ignore and mark this specific single instance of finding as “Accept Risk”
  • #jit_ignore_type_in_file Ignore any finding of type "Applications credentials in configuration files" in src/kubernetes/jit_ecr/templates/script-configmap.yaml; future occurrences will also be ignored.
  • #jit_undo_ignore Undo ignore command

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#jit_ignore_fp

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security control: Iac Misconfig Detection Kubernetes

Type: Applications Credentials In Configuration Files

Description: More details in the link below

Severity: HIGH

Learn more about this issue


Jit Bot commands and options (e.g., ignore issue)

You can trigger Jit actions by commenting on this PR review:

  • #jit_ignore_fp Ignore and mark this specific single instance of finding as “False Positive”
  • #jit_ignore_accept Ignore and mark this specific single instance of finding as “Accept Risk”
  • #jit_ignore_type_in_file Ignore any finding of type "Applications credentials in configuration files" in src/kubernetes/jit_ecr/templates/script-configmap.yaml; future occurrences will also be ignored.
  • #jit_undo_ignore Undo ignore command

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#jit_ignore_fp

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
Loading