Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ metadata:
rules:
- apiGroups: [""]
resources: ["secrets","serviceaccounts/token"]
verbs: ["get", "watch", "list", "create", "patch"]
verbs: ["get", "watch", "list", "create", "patch", "delete"]

---
kind: RoleBinding
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Increment this value whenever you make a change to an immutable field of the Job
E.g. passing in a new environment variable.
Included in $_job_hash (see below).
*/}}
{{- $_job_version := "v2" }}
{{- $_job_version := "v3" }}

{{- /*
10 char hash appended to the job name taking into account $_job_config_values, $_job_version and $_cli_image_digest
Expand Down Expand Up @@ -124,20 +124,50 @@ spec:
SM_AWS_ACCESS_KEY_ID=$(cat /etc/mas/creds/aws/aws_access_key_id)
SM_AWS_SECRET_ACCESS_KEY=$(cat /etc/mas/creds/aws/aws_secret_access_key)

# Get name of secret generated for the custom service account
# As of kubernetes 1.24 (OCP 4.11+), tokens are no longer automatically generated for service accounts.
# On Kubernetes 1.27+ (OCP 4.17+) tokens must be created manually using oc create token.
# The bound object (secret) must exist before oc create token can bind to it.
SECRET_NAME="${CUSTOM_SA_NAME}-token"
EXISTING_TOKEN=$(oc get secret ${SECRET_NAME} -n ${CUSTOM_SA_NAMESPACE} --ignore-not-found -o json | jq -r '.data.token // empty')
if [[ -z "${EXISTING_TOKEN}" ]]; then
echo "Token not found in secret ${SECRET_NAME}; creating secret and generating bound token"
oc create secret generic ${SECRET_NAME} --from-literal=token=placeholder -n ${CUSTOM_SA_NAMESPACE} --dry-run=client -o yaml | oc apply -f -
SECRET_TOKEN=$(oc create token ${CUSTOM_SA_NAME} --bound-object-kind Secret --bound-object-name ${SECRET_NAME} -n ${CUSTOM_SA_NAMESPACE} -o json | jq -r '.status.token')
echo "Updating token secret ${SECRET_NAME} with generated token"
oc patch secret ${SECRET_NAME} -n ${CUSTOM_SA_NAMESPACE} --type merge -p "{\"data\":{\"token\":\"$(echo -n ${SECRET_TOKEN} | base64 -w 0)\"}}"
# Search for a pre-existing service-account-token secret bound to this SA via annotation
# kubernetes.io/service-account.name. This correctly detects secrets with any name, including
# those with a random suffix like <sa-name>-token-<suffix> created by older Kubernetes versions.
SECRET_NAME=$(oc get secret -n ${CUSTOM_SA_NAMESPACE} \
-o jsonpath="{range .items[?(@.type=='kubernetes.io/service-account-token')]}{.metadata.annotations.kubernetes\\.io/service-account\\.name}{'|'}{.metadata.name}{'\n'}{end}" \
| grep "^${CUSTOM_SA_NAME}|" \
| head -1 \
| cut -d'|' -f2 || true)

if [[ -z "${SECRET_NAME}" ]]; then
# No existing service-account-token secret found for this SA.
# The old automation may have left a stale secret with the wrong type (e.g. Opaque).
# Since secret type is immutable in Kubernetes, delete it if it exists so we can
# recreate it with the correct type kubernetes.io/service-account-token.
SECRET_NAME="${CUSTOM_SA_NAME}-token"
if oc get secret ${SECRET_NAME} -n ${CUSTOM_SA_NAMESPACE} --ignore-not-found -o name | grep -q .; then
echo "Found stale secret ${SECRET_NAME} with incorrect type; deleting so it can be recreated correctly"
oc delete secret ${SECRET_NAME} -n ${CUSTOM_SA_NAMESPACE}
fi
echo "Creating secret ${SECRET_NAME} with type kubernetes.io/service-account-token for ${CUSTOM_SA_NAME}"
oc create secret generic ${SECRET_NAME} \
--type=kubernetes.io/service-account-token \
-n ${CUSTOM_SA_NAMESPACE} \
--dry-run=client -o yaml \
| oc annotate --local -f - "kubernetes.io/service-account.name=${CUSTOM_SA_NAME}" -o yaml \
| oc apply -f -
# Wait for Kubernetes to populate the token into the secret (up to 30s)
echo "Waiting for Kubernetes to populate token in secret ${SECRET_NAME}"
TOKEN_POPULATED=false
for i in $(seq 1 30); do
TOKEN_CHECK=$(oc get secret ${SECRET_NAME} -n ${CUSTOM_SA_NAMESPACE} -o jsonpath='{.data.token}' 2>/dev/null)
if [[ -n "${TOKEN_CHECK}" ]]; then
echo "Token populated in secret ${SECRET_NAME} after ${i}s"
TOKEN_POPULATED=true
break
fi
sleep 1
done
if [[ "${TOKEN_POPULATED}" != "true" ]]; then
echo "Timed out waiting for Kubernetes to populate token in secret ${SECRET_NAME}"
exit 1
fi
else
echo "Token secret ${SECRET_NAME} already exists with a token; skipping token generation"
echo "Found existing service-account-token secret ${SECRET_NAME} for ${CUSTOM_SA_NAME}; skipping creation"
fi

# Get secret token to store in sm
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{{- if .Values.cluster_admin_role }}
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: patch-ingress-job
namespace: mas-{{ .Values.instance_id }}-syncres
annotations:
argocd.argoproj.io/sync-wave: "01"
{{- if .Values.custom_labels }}
labels:
{{ .Values.custom_labels | toYaml | indent 4 }}
{{- end }}

---
# Scoped ClusterRole — grants only the minimum permissions required to
# read and patch IngressController resources in openshift-ingress-operator.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: patch-ingress-job-clusterrole-{{ .Values.instance_id }}
annotations:
argocd.argoproj.io/sync-wave: "01"
{{- if .Values.custom_labels }}
labels:
{{ .Values.custom_labels | toYaml | indent 4 }}
{{- end }}
rules:
- apiGroups:
- operator.openshift.io
resources:
- ingresscontrollers
verbs:
- get
- patch

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: patch-ingress-job-clusterrolebinding-{{ .Values.instance_id }}
annotations:
argocd.argoproj.io/sync-wave: "02"
{{- if .Values.custom_labels }}
labels:
{{ .Values.custom_labels | toYaml | indent 4 }}
{{- end }}
subjects:
- kind: ServiceAccount
name: patch-ingress-job
namespace: mas-{{ .Values.instance_id }}-syncres
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: patch-ingress-job-clusterrole-{{ .Values.instance_id }}
{{- end }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
{{- if .Values.cluster_admin_role }}
{{- $masChannel := .Values.mas_channel }}
{{- $versionParts := splitList "." $masChannel }}
{{- $majorVersion := index $versionParts 0 | int }}
{{- $minorVersion := index $versionParts 1 | int }}
{{- if or (gt $majorVersion 9) (and (eq $majorVersion 9) (ge $minorVersion 2)) }}
{{- if eq .Values.mas_routing_mode "path" }}

{{- /*
Meaningful prefix for the job resource name. Must be under 52 chars in length to leave room for the 11 chars reserved for '-' and $_job_hash.
*/}}
{{- $_job_name_prefix := "patch-ingress-ns-ownership" }}

{{- /*
Use the build/bin/set-cli-image-digest.sh script to update this value across all charts.
Included in $_job_hash (see below).
*/}}
{{- $_cli_image_digest := "sha256:1c5701a24c9796e02b33036f56babbe74032831e1db733ce9228a46dce4a870b" }}

{{- /*
A dict of values that influence the behaviour of the job in some way.
Any changes to values in this dict will trigger a rerun of the job.
Since jobs must be idemopotent, it's generally safe to pass in values here that are not
strictly necessary (i.e. including some values that don't actually influence job behaviour).
We may want to refine this further though for jobs that can take a long time to complete.
Included in $_job_hash (see below).
*/}}
{{- $_job_config_values := omit .Values "junitreporter" }}

{{- /*
Increment this value whenever you make a change to an immutable field of the Job resource.
E.g. passing in a new environment variable.
Included in $_job_hash (see below).
*/}}
{{- $_job_version := "v1" }}

{{- /*
10 char hash appended to the job name taking into account $_job_config_values, $_job_version and $_cli_image_digest
This is to ensure ArgoCD will create a new job resource intead of attempting (and failing) to update an
immutable field of any existing Job resource.
*/}}
{{- $_job_hash := print ($_job_config_values | toYaml) $_cli_image_digest $_job_version | adler32sum }}

{{- $_job_name := join "-" (list $_job_name_prefix $_job_hash )}}

{{- /*
Set as the value for the mas.ibm.com/job-cleanup-group label on the Job resource.

When the auto_delete flag is not set on the root application, a CronJob in the cluster uses this label
to identify old Job resources that should be pruned on behalf of ArgoCD.

Any Job resources in the same namespace that have the mas.ibm.com/job-cleanup-group with this value
will be considered to belong to the same cleanup group. All but the most recent (i.e. with the latest "creation_timestamp")
Jobs will be automatically deleted.

$_job_cleanup_group can usually just be based on $_job_name_prefix. There are some special cases
where multiple Jobs are created in our templates using a Helm loop. In those cases, additional descriminators
must be added to $_job_cleanup_group.

By convention, we sha1sum this value to guarantee we never exceed the 63 char limit regardless of which discriminators
are required here.

*/}}
{{- $_job_cleanup_group := cat $_job_name_prefix | sha1sum }}


---
apiVersion: batch/v1
kind: Job
metadata:
name: {{ $_job_name }}
namespace: mas-{{ .Values.instance_id }}-syncres
annotations:
argocd.argoproj.io/sync-wave: "00"
labels:
mas.ibm.com/job-cleanup-group: {{ $_job_cleanup_group }}
{{- if .Values.custom_labels }}
{{ .Values.custom_labels | toYaml | indent 4 }}
{{- end }}
spec:
template:
metadata:
labels:
app: "sync-job"
{{- if .Values.custom_labels }}
{{ .Values.custom_labels | toYaml | indent 8 }}
{{- end }}
spec:
containers:
- name: patch-ingress-ns-ownership
image: {{ .Values.cli_image_repo | default "quay.io/ibmmas/cli" }}@{{ $_cli_image_digest }}
imagePullPolicy: IfNotPresent
env:
- name: MAS_INSTANCE_ID
value: "{{ .Values.instance_id }}"
- name: MAS_INGRESS_CONTROLLER_NAME
value: "{{ .Values.mas_ingress_controller_name | default "default" }}"
command:
- /bin/sh
- -c
- |
set -e

echo ""
echo "================================================================================"
echo "Patch IngressController namespaceOwnership to InterNamespaceAllowed"
echo "================================================================================"
echo "MAS Instance ID ..................... ${MAS_INSTANCE_ID}"
echo "IngressController Name .............. ${MAS_INGRESS_CONTROLLER_NAME}"

CURRENT_OWNERSHIP=$(oc get ingresscontroller "${MAS_INGRESS_CONTROLLER_NAME}" \
-n openshift-ingress-operator \
-o jsonpath='{.spec.routeAdmission.namespaceOwnership}' 2>/dev/null || echo "")

echo "Current namespaceOwnership .......... ${CURRENT_OWNERSHIP:-<not set>}"

if [ "${CURRENT_OWNERSHIP}" = "InterNamespaceAllowed" ]; then
echo "IngressController '${MAS_INGRESS_CONTROLLER_NAME}' is already configured with namespaceOwnership=InterNamespaceAllowed - no changes needed"
exit 0
fi

echo "Patching IngressController '${MAS_INGRESS_CONTROLLER_NAME}' ..."
oc patch ingresscontroller "${MAS_INGRESS_CONTROLLER_NAME}" \
-n openshift-ingress-operator \
--type=merge \
--patch='{"spec":{"routeAdmission":{"namespaceOwnership":"InterNamespaceAllowed"}}}'

rc=$?
echo "patch_ingress_namespace_ownership rc=${rc}"
[ $rc -ne 0 ] && exit $rc
exit 0

restartPolicy: Never
serviceAccountName: patch-ingress-job
backoffLimit: 4
{{- end }}
{{- end }}
{{- end }}
2 changes: 1 addition & 1 deletion instance-applications/120-ibm-db2u-database/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ The job creates a single `USER_AUDIT` policy (idempotent — skipped if already
| Application | Audit Policy | Roles Audited | User Audited |
|---|---|---|---|
| `manage` | `USER_AUDIT` | `MAXIMO_READ`, `MAXIMO_WRITE` (only if roles exist) | `db2inst1` |
| `facilities` | `USER_AUDIT` | `TRIRIGA_READ`, `TRIRIGA_WRITE` (only if roles exist) | `db2inst1` |
| `facilities` | `USER_AUDIT` | `TRIDATA_READ`, `TRIDATA_WRITE` (only if roles exist) | `db2inst1` |
| `monitor` | `USER_AUDIT` | None | `db2inst1` |
| `iot` | `USER_AUDIT` | None | `db2inst1` |

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# Arguments:
# $1 - Schema name to create roles for (required)
# e.g. ./CreateRoles.sh MAXIMO (for Manage DB2)
# ./CreateRoles.sh TRIRIGA (for Facilities DB2)
# ./CreateRoles.sh TRIDATA (for Facilities DB2)
#
################################################################################

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ postsync setup job (sync-wave 129) has completed.

Only executed for applications that use DB2 audit policy:
- manage : AUDIT ROLE MAXIMO_READ, ROLE MAXIMO_WRITE USING POLICY USER_AUDIT
- facilities: AUDIT ROLE TRIRIGA_READ, ROLE TRIRIGA_WRITE USING POLICY USER_AUDIT
- facilities: AUDIT ROLE TRIDATA_READ, ROLE TRIDATA_WRITE USING POLICY USER_AUDIT
- monitor : no role audit - db2inst1 user audit only
- iot : no role audit - db2inst1 user audit only

Expand Down Expand Up @@ -165,21 +165,21 @@ spec:
fi
ROLE_LABEL='MAXIMO_READ, MAXIMO_WRITE'
elif [ \"${MAS_APP_ID}\" = \"facilities\" ]; then
ROLE_READ_EXISTS=\$(db2 -x \"SELECT COUNT(*) FROM SYSCAT.ROLES WHERE ROLENAME='TRIRIGA_READ'\" | tr -d ' ')
ROLE_WRITE_EXISTS=\$(db2 -x \"SELECT COUNT(*) FROM SYSCAT.ROLES WHERE ROLENAME='TRIRIGA_WRITE'\" | tr -d ' ')
ROLE_READ_EXISTS=\$(db2 -x \"SELECT COUNT(*) FROM SYSCAT.ROLES WHERE ROLENAME='TRIDATA_READ'\" | tr -d ' ')
ROLE_WRITE_EXISTS=\$(db2 -x \"SELECT COUNT(*) FROM SYSCAT.ROLES WHERE ROLENAME='TRIDATA_WRITE'\" | tr -d ' ')
if [ \"\$ROLE_READ_EXISTS\" -gt \"0\" ] && [ \"\$ROLE_WRITE_EXISTS\" -gt \"0\" ]; then
echo ' - TRIRIGA_READ'
db2 \"AUDIT ROLE TRIRIGA_READ USING POLICY USER_AUDIT\" >/dev/null 2>&1 \
echo ' - TRIDATA_READ'
db2 \"AUDIT ROLE TRIDATA_READ USING POLICY USER_AUDIT\" >/dev/null 2>&1 \
&& echo ' [OK] USER_AUDIT assigned.' \
|| echo ' [SKIP] Already assigned.'
echo ' - TRIRIGA_WRITE'
db2 \"AUDIT ROLE TRIRIGA_WRITE USING POLICY USER_AUDIT\" >/dev/null 2>&1 \
echo ' - TRIDATA_WRITE'
db2 \"AUDIT ROLE TRIDATA_WRITE USING POLICY USER_AUDIT\" >/dev/null 2>&1 \
&& echo ' [OK] USER_AUDIT assigned.' \
|| echo ' [SKIP] Already assigned.'
else
echo ' [SKIP] TRIRIGA_READ/TRIRIGA_WRITE roles not found - skipping role audit.'
echo ' [SKIP] TRIDATA_READ/TRIDATA_WRITE roles not found - skipping role audit.'
fi
ROLE_LABEL='TRIRIGA_READ, TRIRIGA_WRITE'
ROLE_LABEL='TRIDATA_READ, TRIDATA_WRITE'
elif [ \"${MAS_APP_ID}\" = \"monitor\" ] || [ \"${MAS_APP_ID}\" = \"iot\" ]; then
ROLE_LABEL='N/A - user audit only'
echo ' [SKIP] No role audit required for ${MAS_APP_ID} - db2inst1 user audit only.'
Expand Down
Loading
Loading