Skip to content

Commit

Permalink
[super-agent] feat: Create identity and mount secret (#1444)
Browse files Browse the repository at this point in the history
Added from this branch to be mergeable with @paologallinaharbur approval
#1442

Co-authored-by: Paolo Gallina <[email protected]>
gsanchezgavier and paologallinaharbur authored Jul 26, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent 1711e26 commit a2a7a07
Showing 11 changed files with 265 additions and 198 deletions.
6 changes: 3 additions & 3 deletions charts/super-agent/Chart.lock
Original file line number Diff line number Diff line change
@@ -4,9 +4,9 @@ dependencies:
version: 2.13.0
- name: super-agent-deployment
repository: ""
version: 0.0.20-beta
version: 0.0.21-beta
- name: common-library
repository: https://helm-charts.newrelic.com
version: 1.2.0
digest: sha256:d25475b5e93bd110455a26233c94ba42fa75556799ad10414a31e0043e446311
generated: "2024-07-24T12:28:38.117429+02:00"
digest: sha256:e6adc107915921837bc843af4426a96f0c6d27a9e5c202204b9a56148b31991e
generated: "2024-07-25T18:12:09.459639+02:00"
4 changes: 2 additions & 2 deletions charts/super-agent/Chart.yaml
Original file line number Diff line number Diff line change
@@ -3,15 +3,15 @@ name: super-agent
description: Bootstraps New Relic' Super Agent

type: application
version: 0.0.14-beta
version: 0.0.15-beta

dependencies:
- name: flux2
repository: https://fluxcd-community.github.io/helm-charts
version: 2.13.0
condition: flux2.enabled
- name: super-agent-deployment
version: 0.0.20-beta
version: 0.0.21-beta
condition: super-agent-deployment.enabled
# The following dependency is needed as sub-dependency of super-agent-deployment
- name: common-library
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ description: A Helm chart to install New Relic Super agent on Kubernetes

type: application

version: 0.0.20-beta
version: 0.0.21-beta

keywords:
- newrelic
Original file line number Diff line number Diff line change
@@ -5,6 +5,36 @@ Return the name of the configMap holding the Super Agent's config. Defaults to r
{{- (include "newrelic.common.naming.truncateToDNSWithSuffix" ( dict "name" "local-data" "suffix" "superagent-config" )) -}}
{{- end -}}

{{- /*
Return to which endpoint should the super agent ask to renew its token
*/ -}}
{{- define "newrelic-super-agent.config.endpoints.tokenRenewal" -}}
{{- if include "newrelic.common.nrStaging" . -}}
https://system-identity-oauth.staging-service.newrelic.com/oauth2/token
{{- else if .Values.euEndpoints -}}
https://system-identity-oauth.service.eu.newrelic.com/oauth2/token
{{- else -}}
https://system-identity-oauth.service.newrelic.com/oauth2/token
{{- end -}}
{{- end -}}



{{- /*
Return to which endpoint should the super agent register its system identity
*/ -}}
{{- define "newrelic-super-agent.config.endpoints.systemIdentityRegistration" -}}
{{- if include "newrelic.common.nrStaging" . -}}
https://staging-api.newrelic.com/graphql
{{- else if .Values.euEndpoints -}}
https://api.eu.newrelic.com/graphql
{{- else -}}
https://api.newrelic.com/graphql
{{- end -}}
{{- end -}}



{{- /*
Builds the configuration from config on the values and add more config options like
cluster name, licenses, and custom attributes
@@ -23,6 +53,18 @@ If you need a list of TODOs, just `grep TODO` on the `values.yaml` and look for
{{- $config := .Values.config.superAgent.content | default dict -}}
{{- $config = mustMergeOverwrite (dict "k8s" (dict "cluster_name" (include "newrelic.common.cluster" .))) $config -}}
{{- $config = mustMergeOverwrite (dict "k8s" (dict "namespace" .Release.Namespace)) $config -}}

{{- if .Values.config.superAgent.content -}}
{{- if .Values.config.superAgent.content.opamp -}}
{{- if .Values.config.auth }}
{{- if .Values.config.auth.enabled -}}
{{- $opamp := (dict "opamp" (dict "auth_config" (dict "token_url" (include "newrelic-super-agent.config.endpoints.tokenRenewal" .) "provider" "local" "private_key_path" "/etc/newrelic-super-agent/keys/from-secret.key"))) -}}
{{- $_ := $opamp | mustMergeOverwrite $config -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}

{{- $config | toYaml -}}
{{- end -}}

@@ -46,31 +88,25 @@ readOnlyRootFilesystem: true
{{- end -}}
{{- end -}}





{{- /*
Check if authSecret.create is explicitly set to true. If authSecret is not empty and create is not defined, default it to false.
Return .Values.config.auth.organizationId and fails if it does not exists
*/ -}}
{{- define "newrelic-super-agent.shouldCreateAuthSecret" -}}
{{- $authSecret := .Values.authSecret }}
{{- if and (hasKey $authSecret "create") }}
{{- toYaml $authSecret.create -}}
{{- else if not (empty $authSecret) }}
{{- toYaml false -}}
{{- else }}
{{- toYaml false -}}
{{- end }}
{{- define "newrelic-super-agent.auth.organizationId" -}}
{{- if not ((.Values.config).auth).organizationId -}}
{{- fail ".Values.config.auth.organizationId is required." -}}
{{- end -}}
{{- .Values.config.auth.organizationId -}}
{{- end -}}



{{- /*
Check if authSecret.data and auth_key are provided. Fail if not.
Releases with "-auth" suffix.
*/ -}}
{{- define "newrelic-super-agent.authSecret.validateData" -}}
{{- $authSecret := .Values.authSecret }}
{{- if and $authSecret (not (empty $authSecret)) }}
{{- if not $authSecret.data }}
{{- fail "authSecret.data must be provided when authSecret.create is true" }}
{{- end }}
{{- if not $authSecret.data.auth_key }}
{{- fail "auth_key must be provided when authSecret.create is true" }}
{{- end }}
{{- end }}
{{- define "newrelic-super-agent.auth.secret.name" -}}
{{- include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "auth") }}
{{- end -}}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -55,6 +55,15 @@ spec:
imagePullPolicy: {{ .Values.image.pullPolicy }}

env:
{{- if .Values.config.auth }}
{{- if .Values.config.auth.enabled }}
- name: NR_SA_OPAMP__AUTH_CONFIG__CLIENT_ID
valueFrom:
secretKeyRef:
name: {{ include "newrelic-super-agent.auth.secret.name" . }}
key: CLIENT_ID
{{- end }}
{{- end }}
- name: NR_LICENSE_KEY
valueFrom:
secretKeyRef:
@@ -84,14 +93,15 @@ spec:
- mountPath: /var/lib/newrelic-super-agent
name: var-lib-newrelic-super-agent
readOnly: false
{{- if .Values.config.auth }}
{{- if .Values.config.auth.enabled }}
- name: auth-secret-private-key
mountPath: "/etc/newrelic-super-agent/keys"
{{- end }}
{{- end }}
{{- with .Values.extraVolumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if eq (include "newrelic-super-agent.shouldCreateAuthSecret" .) "true" }}
- name: auth-secret-volume
mountPath: "/etc/newrelic-super-agent"
readOnly: true
{{- end }}

resources:
{{- toYaml .Values.resources | nindent 12 }}
@@ -104,14 +114,19 @@ spec:
path: config.yaml
- name: var-lib-newrelic-super-agent
emptyDir: {}
{{- if .Values.config.auth }}
{{- if .Values.config.auth.enabled }}
- name: auth-secret-private-key
secret:
secretName: {{ include "newrelic-super-agent.auth.secret.name" . }}
items:
- key: private_key
path: from-secret.key
{{- end }}
{{- end }}
{{- with .Values.extraVolumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if eq (include "newrelic-super-agent.shouldCreateAuthSecret" .) "true" }}
- name: auth-secret-volume
secret:
secretName: {{ .Values.authSecret.name | default .Release.Name }}
{{- end }}

{{- with include "newrelic.common.nodeSelector" . }}
nodeSelector:
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
{{- if .Values.config.auth -}}
{{- if and .Values.config.auth.enabled .Values.config.auth.secret.create -}}
apiVersion: batch/v1
kind: Job
metadata:
annotations:
helm.sh/hook: pre-install # TODO we cannot enable auth after installation, we should add pre-upgrade and check if the secret exists
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
name: {{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "system-identity-installer" ) }}
namespace: {{ .Release.Namespace }}
spec:
ttlSecondsAfterFinished: 120
template:
spec:
restartPolicy: Never
serviceAccountName: {{ include "newrelic.common.serviceAccount.name" . }}-auth
containers:
- name: register-system-identity
image: alpine
command:
- ash
args:
- -c
- |
set -uo pipefail
apk update
apk add curl kubectl jq openssl
TEMPORAL_FOLDER=gen-folder
mkdir $TEMPORAL_FOLDER
openssl genrsa -out "$TEMPORAL_FOLDER/key" 4096
openssl rsa -in "$TEMPORAL_FOLDER/key" -pubout -out "$TEMPORAL_FOLDER/pub"
for RETRY in 1 2 3; do
HTTP_CODE=$(echo '{ "query":
"mutation {
systemIdentityCreate(
name: \"System Identity for Kubernetes cluster '{{ include "newrelic.common.cluster" . }}'\",
organizationId: \"{{ include "newrelic-super-agent.auth.organizationId" . }}\",
publicKey: \"'$(openssl enc -base64 -A -in "$TEMPORAL_FOLDER/pub")'\"
) {
clientId,
name
}
}"
}' | tr -d $'\n' | \
curl \
-s -w "%{http_code}" \
-H "Content-Type: application/json" \
-H "API-Key: {{ .Values.config.auth.userApiKey }}" \
-o "$TEMPORAL_FOLDER/response.json" \
--data @- \
"{{ include "newrelic-super-agent.config.endpoints.systemIdentityRegistration" . }}"
)
if [ $HTTP_CODE -eq 200 ]; then
break
fi
echo "Error creating the new system identity. The API endpoint returned $HTTP_CODE. Retrying ($RETRY/3)..."
sleep 2
done
# Retry mechanism failed. Exiting...
if [ $HTTP_CODE -ne 200 ]; then exit 1; fi
ERROR_MESSAGE=$(jq -r '.errors[0].message // "NOERROR"' "$TEMPORAL_FOLDER/response.json")
if [ "$ERROR_MESSAGE" != "NOERROR" ]; then
echo "Error creating an identity: $ERROR_MESSAGE"
exit 1
fi
kubectl create secret generic --dry-run -o json \
{{ include "newrelic-super-agent.auth.secret.name" . }} \
--from-literal=CLIENT_ID=$(jq -r '.data.systemIdentityCreate.clientId' "$TEMPORAL_FOLDER/response.json") \
--from-file="private_key=$TEMPORAL_FOLDER/key" | \
jq '.metadata.labels |= {{ include "newrelic.common.labels" . | fromYaml | toJson }}' | \
kubectl apply -n "{{ .Release.Namespace }}" -f -
---
{{ if .Values.rbac.create }}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
annotations:
helm.sh/hook: pre-install
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
labels:
{{- include "newrelic.common.labels" . | nindent 4 }}
name: {{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "auth") }}
namespace: {{ .Release.Namespace }}
rules:
- apiGroups: [ "" ]
resources: ["secrets"]
verbs:
- create
- patch
- update
- get
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
annotations:
helm.sh/hook: pre-install
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
labels:
{{- include "newrelic.common.labels" . | nindent 4 }}
name: {{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "auth") }}
namespace: {{ .Release.Namespace }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "auth") }}
subjects:
- kind: ServiceAccount
name: {{ include "newrelic.common.serviceAccount.name" . }}-auth
namespace: {{ .Release.Namespace }}
{{- end -}}

{{ if include "newrelic.common.serviceAccount.create" . }}
---
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
helm.sh/hook: pre-install
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
{{- if include "newrelic.common.serviceAccount.annotations" . }}
{{- include "newrelic.common.serviceAccount.annotations" . | nindent 4 }}
{{- end }}
labels:
{{- include "newrelic.common.labels" . | nindent 4 }}
name: {{ include "newrelic.common.serviceAccount.name" . }}-auth
namespace: {{ .Release.Namespace }}
{{- end -}}
{{- end -}}
{{- end -}}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{{- if .Values.cleanupManagedResources -}}
{{- $uninstallJobName := include "newrelic.common.naming.truncateToDNSWithSuffix" ( dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "deployment-uninstall-job" ) -}}
{{- $uninstallJobName := include "newrelic.common.naming.truncateToDNSWithSuffix" ( dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "deployment-uninstall" ) -}}
{{- /*
The resources managed by the super-agent and the label selector are hardcoded on the super-agent.
*/ -}}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -77,3 +77,32 @@ tests:
enabled: true
test: value
test2: value2
- it: super agent's config always include cluster_name, namespace and defaults
set:
cluster: my-cluster
config:
superAgent:
content:
opamp:
endpoint: https://opamp.service.eu.newrelic.com/v1/opamp
auth:
enabled: true
euEndpoints: true
asserts:
- equal:
path: data["local_config"]
value: |
agents:
open-telemetry:
agent_type: newrelic/io.opentelemetry.collector:0.2.0
k8s:
cluster_name: my-cluster
namespace: my-namespace
opamp:
auth_config:
private_key_path: /etc/newrelic-super-agent/keys/from-secret.key
provider: local
token_url: https://system-identity-oauth.service.eu.newrelic.com/oauth2/token
endpoint: https://opamp.service.eu.newrelic.com/v1/opamp
server:
enabled: true
36 changes: 14 additions & 22 deletions charts/super-agent/values.yaml
Original file line number Diff line number Diff line change
@@ -91,9 +91,12 @@ super-agent-deployment:
proxy: ""

# -- (bool) Send the metrics to the staging backend. Requires a valid staging license key. Can be configured also with `global.nrStaging`
# When enabled, in case `authSecret.create` is set to `true`, OpAMP `endpoint` and auth `token_url` need to be updated.
# @default -- `false`
nrStaging: # TODO: Update OpAMP `endpoint` and auth `token_url` for staging and Europe when nrStaging is enabled.
nrStaging:

# -- (bool) Changes default endpoint to point to EU backend.
# @default -- `false`
euEndpoints:

fedramp:
# -- (bool) TODO: Enables FedRAMP. Can be configured also with `global.fedramp.enabled`
@@ -108,19 +111,14 @@ super-agent-deployment:
# If disabled, agents and / or agent configurations managed by the super-agent will not be deleted when the chart is uninstalled.
cleanupManagedResources: true

# -- Settings controlling authentication secret creation.
# If `create` is true, a Kubernetes secret will be created containing a key named `auth_key`.
# This secret will be mounted in the deployment pod at the path `/etc/newrelic-super-agent/auth_key`
# for authentication purposes.
authSecret:
create: false
# -- The name of the Kubernetes secret to use or create.
# name: auth-secret
# -- Data to include in the secret. The key should be an RSA256 value.
# data:
# auth_key: ""

config:
auth:
enabled: false
organizationId:
userApiKey:
secret:
create: true

# -- Configuration for the Super Agent.
# @default -- See `values.yaml`
superAgent:
@@ -136,14 +134,7 @@ super-agent-deployment:
# opamp:
# # TODO The endpoint should be set automatically based on the licenseKey and on the nrStaging option if opamp.enable=true
# endpoint: https://opamp.service.newrelic.com/v1/opamp
# headers:
# # TODO this value should be automatically injected
# api-key: LICENSE_KEY

# # -- Note: To use the authentication configuration, ensure authSecret is configured.
# auth_config:
# token_url: "http://fake.com/oauth2/v1/token"
# client_id: "fake"
# endpoint: https://opamp.service.eu.newrelic.com/v1/opamp

# -- This option enables a status server that can be useful for troubleshooting.
# -- Port-forward it `$ kubectl port-forward pod/{pod-name} 51200:51200`
@@ -169,6 +160,7 @@ super-agent-deployment:
# customSecretName: ""
# customSecretLicenseKey: ""


# -- Values for the Flux chat. Ref.: https://github.com/fluxcd-community/helm-charts/blob/flux2-2.10.2/charts/flux2/values.yaml
# @default -- See `values.yaml`
flux2:

0 comments on commit a2a7a07

Please sign in to comment.