Added x509 and jwt rapps

Change-Id: Ic384fcad11dcb63fe4265d3dbcff5ea17f933cfc
diff --git a/rapps/Dockerfile_rra b/rapps/Dockerfile_rra
new file mode 100644
index 0000000..6958e79
--- /dev/null
+++ b/rapps/Dockerfile_rra
@@ -0,0 +1,6 @@
+FROM golang:latest
+RUN mkdir /app
+COPY ./rapps-rapp-auth-provider /app
+RUN chmod +x /app/rapps-rapp-auth-provider
+WORKDIR /app
+ENTRYPOINT ["/app/rapps-rapp-auth-provider"]
diff --git a/rapps/Dockerfile_rrji b/rapps/Dockerfile_rrji
new file mode 100644
index 0000000..238d90b
--- /dev/null
+++ b/rapps/Dockerfile_rrji
@@ -0,0 +1,6 @@
+FROM golang:latest
+RUN mkdir /app
+COPY ./rapps-rapp-jwt-invoker /app
+RUN chmod +x /app/rapps-rapp-jwt-invoker
+WORKDIR /app
+ENTRYPOINT ["/app/rapps-rapp-jwt-invoker"]
diff --git a/rapps/Dockerfile_rrjp b/rapps/Dockerfile_rrjp
new file mode 100644
index 0000000..e109a8f
--- /dev/null
+++ b/rapps/Dockerfile_rrjp
@@ -0,0 +1,6 @@
+FROM golang:latest
+RUN mkdir /app
+COPY ./rapps-rapp-jwt-provider /app
+RUN chmod +x /app/rapps-rapp-jwt-provider
+WORKDIR /app
+ENTRYPOINT ["/app/rapps-rapp-jwt-provider"]
diff --git a/rapps/Dockerfile_rrxi b/rapps/Dockerfile_rrxi
new file mode 100644
index 0000000..ca5675c
--- /dev/null
+++ b/rapps/Dockerfile_rrxi
@@ -0,0 +1,6 @@
+FROM golang:latest
+RUN mkdir /app
+RUN mkdir /certs
+COPY ./rapps-rapp-x509-invoker /app
+RUN chmod +x /app/rapps-rapp-x509-invoker
+WORKDIR /app
diff --git a/rapps/Dockerfile_rrxp b/rapps/Dockerfile_rrxp
new file mode 100644
index 0000000..dfc4b30
--- /dev/null
+++ b/rapps/Dockerfile_rrxp
@@ -0,0 +1,6 @@
+FROM golang:latest
+RUN mkdir /app
+COPY ./rapps-rapp-x509-provider /app
+RUN chmod +x /app/rapps-rapp-x509-provider
+WORKDIR /app
+ENTRYPOINT ["/app/rapps-rapp-x509-provider"]
diff --git a/rapps/IstioOperator.yaml b/rapps/IstioOperator.yaml
new file mode 100644
index 0000000..83604e7
--- /dev/null
+++ b/rapps/IstioOperator.yaml
@@ -0,0 +1,45 @@
+apiVersion: install.istio.io/v1alpha1
+kind: IstioOperator
+spec:
+  profile: demo
+  meshConfig:
+    accessLogEncoding: TEXT
+    accessLogFile: "/dev/stdout"
+    accessLogFormat: ""
+    outboundTrafficPolicy:
+      mode: REGISTRY_ONLY
+  values:
+    pilot:
+      jwksResolverExtraRootCA: |
+        -----BEGIN CERTIFICATE-----
+        MIIFdTCCA12gAwIBAgIUb2mMsNxZ3fpdLt0memNEwSs+yCUwDQYJKoZIhvcNAQEL
+        BQAwSjELMAkGA1UEBhMCSUUxDDAKBgNVBAsMA0VTVDERMA8GA1UEAwwIZXN0LnRl
+        Y2gxGjAYBgkqhkiG9w0BCQEWC2NhQG1haWwuY29tMB4XDTIyMDMyOTEyMjMxOVoX
+        DTMyMDMyNjEyMjMxOVowSjELMAkGA1UEBhMCSUUxDDAKBgNVBAsMA0VTVDERMA8G
+        A1UEAwwIZXN0LnRlY2gxGjAYBgkqhkiG9w0BCQEWC2NhQG1haWwuY29tMIICIjAN
+        BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAy2E9PlJyZBh64gxnqrhB7ELS59TK
+        mKRZEgyWYCyS54sOsVLt7HwrzeHxMqhccyEB3+S0jqgVFe7RQ2dEIqc9H1upG2TH
+        Cznz7+epYXFj7wRfQAXM53mEJYIVPcjJ31iFBHKURC6l/ZBLENNG+mXBN9cO7nMe
+        b99w8Sc5jVMy9VmKDZMzJildtWhyEGEDq4C69TAJq8zfvPExkOZW9iSg25FaCoip
+        IO19EYVxl6BYnjgKr48s1XyREBUnOkw6IeVLzD/2co5UpJd40yolXAG8eDxxSGzT
+        EjyVMR3tph86FQ8H053lYB5Y3u6iwCdALf9TvUpEv+ZL4BcB+I4U0RdtLQGL2iuv
+        9NLeqVAfmtXC3st+DgukxvJA3+iGDGyssvY3EF3eCB9QnjjbDwvZ4raG4DIcBNQ3
+        FfpfpoSswXI4KU2JXgS/V28Az46NIFwwT3WvwhFT5aCUcInNPAF2vDSUfDvlHl39
+        BSSKAqsPnvJIDTnlmJoSo28uca2SkSkXL2N43vGOPV4/UYRIz+bqSFNfu48nfe1I
+        E83PKTCTDum+iOscteF1xMU3KrWLpdkBzPW1PfVK6OcAgbKZvfBGNdNOmygfMj5t
+        Slw0bc2Gpd1ISJyQK0L2DVOSMeB6+PyDdJEYUVe+Xh2uqnaGJnAS90//X/FiOJrJ
+        Y5GrgeVLAkDyOjcCAwEAAaNTMFEwHQYDVR0OBBYEFHoCuHWgHsN1cS7TRuJgk1Yv
+        deY7MB8GA1UdIwQYMBaAFHoCuHWgHsN1cS7TRuJgk1YvdeY7MA8GA1UdEwEB/wQF
+        MAMBAf8wDQYJKoZIhvcNAQELBQADggIBALp0D+Sw09OxZhq8CGw/fQn+AScY9JSE
+        E/4C+jVwSVygi7BKcJfqy8aq7cGe+O9sAEnmxDrle1oECVIXX+mhhS7cD5kRdOsb
+        WAjJBqi+B6YgNuawLfQldnHJV/opjb0FBytaGpEMWYsAj0xcoVe4Nj/x7myQ4qoD
+        Y8r8wEFriOwTk+0dICg40I2EUeq5qoJ7Q5bbdYPfe8EhJAkN4u7xJ6P6GDY6Zvoo
+        JpYSSAaKLZb9yd4SxAoDvyuEZL6YNX8vgfPEZqVi2lm5uDkeE+xqWhL2j0ECKXPN
+        PLQMFBCaVPO9RueiwV/P/l0DuChY7dSAHn9kqdS6PlSGe411OGTpxz5laD9Ho4a9
+        UOAurbtu76wAPnsxszAxMAGqEXvZgcX+zUBm4uGPpLUu5vIiWgE/DpwmIpT5jwDu
+        EV0e7C43q3kT5ieqzxDb3gvUWdQZ4Qg6qa8js7KfKH7L0ToCtZACnpdVXjxE1Mp6
+        aCKAPPo8AJm2YdS0Zyj1w8ZN6tDStZ6sfFyEkcRiLOF0pL0qJKw/aqgZd0cHCZed
+        z9p+zpuSbJgnEqax0G7fF5hGofUuCIz4F8CNiehjpZDrCHqPrbCsUveu4iP+cw2N
+        /DZsEJUr0qL+QsAll2L6Zm8z1bAGxomxfFqUAHPep+msFyKT6W2SXz3MzTClq1JK
+        CruKkw029sEv
+        -----END CERTIFICATE-----
diff --git a/rapps/auth.yaml b/rapps/auth.yaml
new file mode 100644
index 0000000..9b091ad
--- /dev/null
+++ b/rapps/auth.yaml
@@ -0,0 +1,46 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: auth-code-deployment
+  namespace: default
+  labels:
+    app: auth-code
+spec:
+  selector:
+    matchLabels:
+      app: auth-code
+  template:
+    metadata:
+      labels:
+        app: auth-code 
+        version: v1
+    spec:
+      containers:
+      - name: auth-code
+        image: ktimoney/rapps-rapp-auth-provider
+        imagePullPolicy: IfNotPresent
+        ports:
+        - containerPort: 9000 
+        resources:
+          limits:
+            memory: 256Mi
+            cpu: "250m"
+          requests:
+            memory: 128Mi
+            cpu: "80m"
+  replicas: 1 
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: auth-code
+  namespace: default
+spec:
+  selector:
+    app: auth-code
+  ports:
+    - protocol: TCP
+      port: 80
+      targetPort: 9000 
+      nodePort: 31233
+  type: NodePort
diff --git a/rapps/charts/rapp-jwt-invoker/.helmignore b/rapps/charts/rapp-jwt-invoker/.helmignore
new file mode 100644
index 0000000..0e8a0eb
--- /dev/null
+++ b/rapps/charts/rapp-jwt-invoker/.helmignore
@@ -0,0 +1,23 @@
+# Patterns to ignore when building packages.
+# This supports shell glob matching, relative path matching, and
+# negation (prefixed with !). Only one pattern per line.
+.DS_Store
+# Common VCS dirs
+.git/
+.gitignore
+.bzr/
+.bzrignore
+.hg/
+.hgignore
+.svn/
+# Common backup files
+*.swp
+*.bak
+*.tmp
+*.orig
+*~
+# Various IDEs
+.project
+.idea/
+*.tmproj
+.vscode/
diff --git a/rapps/charts/rapp-jwt-invoker/Chart.yaml b/rapps/charts/rapp-jwt-invoker/Chart.yaml
new file mode 100644
index 0000000..843ff42
--- /dev/null
+++ b/rapps/charts/rapp-jwt-invoker/Chart.yaml
@@ -0,0 +1,24 @@
+apiVersion: v2
+name: rapp-jwt-invoker
+description: A Helm chart for Kubernetes
+
+# A chart can be either an 'application' or a 'library' chart.
+#
+# Application charts are a collection of templates that can be packaged into versioned archives
+# to be deployed.
+#
+# Library charts provide useful utilities or functions for the chart developer. They're included as
+# a dependency of application charts to inject those utilities and functions into the rendering
+# pipeline. Library charts do not define any templates and therefore cannot be deployed.
+type: application
+
+# This is the chart version. This version number should be incremented each time you make changes
+# to the chart and its templates, including the app version.
+# Versions are expected to follow Semantic Versioning (https://semver.org/)
+version: 0.1.0
+
+# This is the version number of the application being deployed. This version number should be
+# incremented each time you make changes to the application. Versions are not expected to
+# follow Semantic Versioning. They should reflect the version the application is using.
+# It is recommended to use it with quotes.
+appVersion: "1.16.0"
diff --git a/rapps/charts/rapp-jwt-invoker/templates/NOTES.txt b/rapps/charts/rapp-jwt-invoker/templates/NOTES.txt
new file mode 100644
index 0000000..7955f62
--- /dev/null
+++ b/rapps/charts/rapp-jwt-invoker/templates/NOTES.txt
@@ -0,0 +1,22 @@
+1. Get the application URL by running these commands:
+{{- if .Values.ingress.enabled }}
+{{- range $host := .Values.ingress.hosts }}
+  {{- range .paths }}
+  http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
+  {{- end }}
+{{- end }}
+{{- else if contains "NodePort" .Values.service.type }}
+  export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "rapp-jwt-invoker.fullname" . }})
+  export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
+  echo http://$NODE_IP:$NODE_PORT
+{{- else if contains "LoadBalancer" .Values.service.type }}
+     NOTE: It may take a few minutes for the LoadBalancer IP to be available.
+           You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "rapp-jwt-invoker.fullname" . }}'
+  export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "rapp-jwt-invoker.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
+  echo http://$SERVICE_IP:{{ .Values.service.port }}
+{{- else if contains "ClusterIP" .Values.service.type }}
+  export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "rapp-jwt-invoker.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
+  export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
+  echo "Visit http://127.0.0.1:8080 to use your application"
+  kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
+{{- end }}
diff --git a/rapps/charts/rapp-jwt-invoker/templates/_helpers.tpl b/rapps/charts/rapp-jwt-invoker/templates/_helpers.tpl
new file mode 100644
index 0000000..f74ce3a
--- /dev/null
+++ b/rapps/charts/rapp-jwt-invoker/templates/_helpers.tpl
@@ -0,0 +1,62 @@
+{{/*
+Expand the name of the chart.
+*/}}
+{{- define "rapp-jwt-invoker.name" -}}
+{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Create a default fully qualified app name.
+We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
+If release name contains chart name it will be used as a full name.
+*/}}
+{{- define "rapp-jwt-invoker.fullname" -}}
+{{- if .Values.fullnameOverride }}
+{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- $name := default .Chart.Name .Values.nameOverride }}
+{{- if contains $name .Release.Name }}
+{{- .Release.Name | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
+{{- end }}
+{{- end }}
+{{- end }}
+
+{{/*
+Create chart name and version as used by the chart label.
+*/}}
+{{- define "rapp-jwt-invoker.chart" -}}
+{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Common labels
+*/}}
+{{- define "rapp-jwt-invoker.labels" -}}
+helm.sh/chart: {{ include "rapp-jwt-invoker.chart" . }}
+{{ include "rapp-jwt-invoker.selectorLabels" . }}
+{{- if .Chart.AppVersion }}
+app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
+{{- end }}
+app.kubernetes.io/managed-by: {{ .Release.Service }}
+{{- end }}
+
+{{/*
+Selector labels
+*/}}
+{{- define "rapp-jwt-invoker.selectorLabels" -}}
+app.kubernetes.io/name: {{ include "rapp-jwt-invoker.name" . }}
+app.kubernetes.io/instance: {{ .Release.Name }}
+{{- end }}
+
+{{/*
+Create the name of the service account to use
+*/}}
+{{- define "rapp-jwt-invoker.serviceAccountName" -}}
+{{- if .Values.serviceAccount.create }}
+{{- default (include "rapp-jwt-invoker.fullname" .) .Values.serviceAccount.name }}
+{{- else }}
+{{- default "default" .Values.serviceAccount.name }}
+{{- end }}
+{{- end }}
diff --git a/rapps/charts/rapp-jwt-invoker/templates/clusterrole.yaml b/rapps/charts/rapp-jwt-invoker/templates/clusterrole.yaml
new file mode 100644
index 0000000..23d1586
--- /dev/null
+++ b/rapps/charts/rapp-jwt-invoker/templates/clusterrole.yaml
@@ -0,0 +1,20 @@
+{{- if .Values.rbac.create -}}
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  labels:
+    app: {{ template "rapp-jwt-invoker.name" .}}
+    chart: {{ .Chart.Name }}-{{ .Chart.Version }}
+    heritage: {{ .Release.Service }}
+    release: {{ .Release.Name }}
+  name: {{ template "rapp-jwt-invoker.fullname" . }}
+  namespace: {{ .Release.Namespace }} 
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: cluster-admin 
+subjects:
+- kind: ServiceAccount
+  name: {{ template "rapp-jwt-invoker.fullname" . }}
+  namespace: {{ .Release.Namespace }}
+{{- end -}}
diff --git a/rapps/charts/rapp-jwt-invoker/templates/deployment.yaml b/rapps/charts/rapp-jwt-invoker/templates/deployment.yaml
new file mode 100644
index 0000000..3bb60bd
--- /dev/null
+++ b/rapps/charts/rapp-jwt-invoker/templates/deployment.yaml
@@ -0,0 +1,78 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: {{ include "rapp-jwt-invoker.fullname" . }}
+  labels:
+    {{- include "rapp-jwt-invoker.labels" . | nindent 4 }}
+spec:
+  {{- if not .Values.autoscaling.enabled }}
+  replicas: {{ .Values.replicaCount }}
+  {{- end }}
+  selector:
+    matchLabels:
+      {{- include "rapp-jwt-invoker.selectorLabels" . | nindent 6 }}
+  template:
+    metadata:
+      {{- with .Values.podAnnotations }}
+      annotations:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      labels:
+        {{- include "rapp-jwt-invoker.selectorLabels" . | nindent 8 }}
+    spec:
+      {{- with .Values.imagePullSecrets }}
+      imagePullSecrets:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      serviceAccountName: {{ include "rapp-jwt-invoker.serviceAccountName" . }}
+      securityContext:
+        {{- toYaml .Values.podSecurityContext | nindent 8 }}
+      containers:
+        - name: {{ .Chart.Name }}
+          securityContext:
+            {{- toYaml .Values.securityContext | nindent 12 }}
+          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
+          imagePullPolicy: {{ .Values.image.pullPolicy }}
+          command: ["/app/rapps-rapp-jwt-invoker"]
+          args: [
+                  "-securityEnabled", "{{ .Values.rapp.securityEnabled }}",
+                  "-realm", "{{ .Values.rapp.realm }}",
+                  "-client", "{{ .Values.rapp.client }}",
+                  "-role",  "{{ with index .Values.rapp.roles 0 }}{{ .role }}{{ end }}",
+                  "-rapp", "{{ with index .Values.rapp.apps 0 }}{{ .prefix }}{{ end }}",
+                  "-methods", "{{- range .Values.rapp.apps }}{{ join "," .methods }}{{- end }}"
+                ]
+          ports:
+            - name: http
+              containerPort: 9000
+              protocol: TCP
+          livenessProbe:
+            httpGet:
+              path: /health
+              port: 9000 
+            initialDelaySeconds: 5 
+            periodSeconds: 60
+          readinessProbe:
+              exec:
+                command: ["stat", "init.txt"]
+          resources:
+            {{- toYaml .Values.resources | nindent 12 }}
+          volumeMounts:
+          - name: jwt-invoker-certs-persistent-storage
+            mountPath: /certs
+      volumes:
+      - name: jwt-invoker-certs-persistent-storage
+        persistentVolumeClaim:
+          claimName: jwt-invoker-certs-storage-pv-claim
+      {{- with .Values.nodeSelector }}
+      nodeSelector:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      {{- with .Values.affinity }}
+      affinity:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      {{- with .Values.tolerations }}
+      tolerations:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
diff --git a/rapps/charts/rapp-jwt-invoker/templates/hpa.yaml b/rapps/charts/rapp-jwt-invoker/templates/hpa.yaml
new file mode 100644
index 0000000..71e704c
--- /dev/null
+++ b/rapps/charts/rapp-jwt-invoker/templates/hpa.yaml
@@ -0,0 +1,28 @@
+{{- if .Values.autoscaling.enabled }}
+apiVersion: autoscaling/v2beta1
+kind: HorizontalPodAutoscaler
+metadata:
+  name: {{ include "rapp-jwt-invoker.fullname" . }}
+  labels:
+    {{- include "rapp-jwt-invoker.labels" . | nindent 4 }}
+spec:
+  scaleTargetRef:
+    apiVersion: apps/v1
+    kind: Deployment
+    name: {{ include "rapp-jwt-invoker.fullname" . }}
+  minReplicas: {{ .Values.autoscaling.minReplicas }}
+  maxReplicas: {{ .Values.autoscaling.maxReplicas }}
+  metrics:
+    {{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
+    - type: Resource
+      resource:
+        name: cpu
+        targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
+    {{- end }}
+    {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
+    - type: Resource
+      resource:
+        name: memory
+        targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
+    {{- end }}
+{{- end }}
diff --git a/rapps/charts/rapp-jwt-invoker/templates/ingress.yaml b/rapps/charts/rapp-jwt-invoker/templates/ingress.yaml
new file mode 100644
index 0000000..e8be03d
--- /dev/null
+++ b/rapps/charts/rapp-jwt-invoker/templates/ingress.yaml
@@ -0,0 +1,41 @@
+{{- if .Values.ingress.enabled -}}
+{{- $fullName := include "rapp-jwt-invoker.fullname" . -}}
+{{- $svcPort := .Values.service.port -}}
+{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
+apiVersion: networking.k8s.io/v1beta1
+{{- else -}}
+apiVersion: extensions/v1beta1
+{{- end }}
+kind: Ingress
+metadata:
+  name: {{ $fullName }}
+  labels:
+    {{- include "rapp-jwt-invoker.labels" . | nindent 4 }}
+  {{- with .Values.ingress.annotations }}
+  annotations:
+    {{- toYaml . | nindent 4 }}
+  {{- end }}
+spec:
+  {{- if .Values.ingress.tls }}
+  tls:
+    {{- range .Values.ingress.tls }}
+    - hosts:
+        {{- range .hosts }}
+        - {{ . | quote }}
+        {{- end }}
+      secretName: {{ .secretName }}
+    {{- end }}
+  {{- end }}
+  rules:
+    {{- range .Values.ingress.hosts }}
+    - host: {{ .host | quote }}
+      http:
+        paths:
+          {{- range .paths }}
+          - path: {{ .path }}
+            backend:
+              serviceName: {{ $fullName }}
+              servicePort: {{ $svcPort }}
+          {{- end }}
+    {{- end }}
+  {{- end }}
diff --git a/rapps/charts/rapp-jwt-invoker/templates/persistentvolume.yaml b/rapps/charts/rapp-jwt-invoker/templates/persistentvolume.yaml
new file mode 100644
index 0000000..5370e21
--- /dev/null
+++ b/rapps/charts/rapp-jwt-invoker/templates/persistentvolume.yaml
@@ -0,0 +1,16 @@
+apiVersion: v1
+kind: PersistentVolume
+metadata:
+  name: jwt-invoker-certs-storage-pv-volume
+  namespace: istio-nonrtric 
+  labels:
+    type: local
+    app: rapp-jwt-invoker 
+spec:
+  storageClassName: manual
+  capacity:
+    storage: 10Mi
+  accessModes:
+    - ReadOnlyMany 
+  hostPath:
+    path: "/var/rapps/certs"
diff --git a/rapps/charts/rapp-jwt-invoker/templates/persistentvolumeclaim.yaml b/rapps/charts/rapp-jwt-invoker/templates/persistentvolumeclaim.yaml
new file mode 100644
index 0000000..022d3f9
--- /dev/null
+++ b/rapps/charts/rapp-jwt-invoker/templates/persistentvolumeclaim.yaml
@@ -0,0 +1,14 @@
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  name: jwt-invoker-certs-storage-pv-claim
+  namespace: istio-nonrtric 
+  labels:
+    app: rapp-jwt-invoker 
+spec:
+  storageClassName: manual
+  accessModes:
+    - ReadOnlyMany 
+  resources:
+    requests:
+      storage: 10Mi
diff --git a/rapps/charts/rapp-jwt-invoker/templates/service.yaml b/rapps/charts/rapp-jwt-invoker/templates/service.yaml
new file mode 100644
index 0000000..2d1b89b
--- /dev/null
+++ b/rapps/charts/rapp-jwt-invoker/templates/service.yaml
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: {{ include "rapp-jwt-invoker.fullname" . }}
+  labels:
+    {{- include "rapp-jwt-invoker.labels" . | nindent 4 }}
+spec:
+  type: {{ .Values.service.type }}
+  ports:
+    - port: {{ .Values.service.port }}
+      targetPort: http
+      protocol: TCP
+      name: http
+  selector:
+    {{- include "rapp-jwt-invoker.selectorLabels" . | nindent 4 }}
diff --git a/rapps/charts/rapp-jwt-invoker/templates/serviceaccount.yaml b/rapps/charts/rapp-jwt-invoker/templates/serviceaccount.yaml
new file mode 100644
index 0000000..09dd89a
--- /dev/null
+++ b/rapps/charts/rapp-jwt-invoker/templates/serviceaccount.yaml
@@ -0,0 +1,12 @@
+{{- if .Values.serviceAccount.create -}}
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: {{ include "rapp-jwt-invoker.serviceAccountName" . }}
+  labels:
+    {{- include "rapp-jwt-invoker.labels" . | nindent 4 }}
+  {{- with .Values.serviceAccount.annotations }}
+  annotations:
+    {{- toYaml . | nindent 4 }}
+  {{- end }}
+{{- end }}
diff --git a/rapps/charts/rapp-jwt-invoker/templates/tests/test-connection.yaml b/rapps/charts/rapp-jwt-invoker/templates/tests/test-connection.yaml
new file mode 100644
index 0000000..357c0f7
--- /dev/null
+++ b/rapps/charts/rapp-jwt-invoker/templates/tests/test-connection.yaml
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Pod
+metadata:
+  name: "{{ include "rapp-jwt-invoker.fullname" . }}-test-connection"
+  labels:
+    {{- include "rapp-jwt-invoker.labels" . | nindent 4 }}
+  annotations:
+    "helm.sh/hook": test
+spec:
+  containers:
+    - name: wget
+      image: busybox
+      command: ['wget']
+      args: ['{{ include "rapp-jwt-invoker.fullname" . }}:{{ .Values.service.port }}']
+  restartPolicy: Never
diff --git a/rapps/charts/rapp-jwt-invoker/values.yaml b/rapps/charts/rapp-jwt-invoker/values.yaml
new file mode 100644
index 0000000..199d1db
--- /dev/null
+++ b/rapps/charts/rapp-jwt-invoker/values.yaml
@@ -0,0 +1,102 @@
+# Default values for rapp-jwt-invoker.
+# This is a YAML-formatted file.
+# Declare variables to be passed into your templates.
+
+replicaCount: 1
+
+image:
+  repository: ktimoney/rapps-rapp-jwt-invoker
+  pullPolicy: IfNotPresent
+  # Overrides the image tag whose default is the chart appVersion.
+  tag: "latest"
+
+imagePullSecrets: []
+nameOverride: ""
+fullnameOverride: ""
+
+serviceAccount:
+  # Specifies whether a service account should be created
+  create: true 
+  # Annotations to add to the service account
+  annotations: {}
+  # The name of the service account to use.
+  # If not set and create is true, a name is generated using the fullname template
+  name: "rapp-jwt-invoker"
+
+rbac:
+  # Specifies whether rbac is enabled 
+  create: true 
+
+podAnnotations: {}
+
+podSecurityContext: {}
+  # fsGroup: 2000
+
+securityContext: {}
+  # capabilities:
+  #   drop:
+  #   - ALL
+  # readOnlyRootFilesystem: true
+  # runAsNonRoot: true
+  # runAsUser: 1000
+
+service:
+  type: ClusterIP 
+  port: 80
+
+ingress:
+  enabled: false
+  annotations: {}
+    # kubernetes.io/ingress.class: nginx
+    # kubernetes.io/tls-acme: "true"
+  hosts:
+    - host: rapp-jwt-invoker
+      paths:
+      - path: /
+        backend:
+          serviceName: rapp-jwt-invoker
+          servicePort: 80
+  tls: []
+  #  - secretName: chart-example-tls
+  #    hosts:
+  #      - chart-example.local
+
+resources: {}
+  # We usually recommend not to specify default resources and to leave this as a conscious
+  # choice for the user. This also increases chances charts run on environments with little
+  # resources, such as Minikube. If you do want to specify resources, uncomment the following
+  # lines, adjust them as necessary, and remove the curly braces after 'resources:'.
+  # limits:
+  #   cpu: 100m
+  #   memory: 128Mi
+  # requests:
+  #   cpu: 100m
+  #   memory: 128Mi
+
+autoscaling:
+  enabled: false
+  minReplicas: 1
+  maxReplicas: 100
+  targetCPUUtilizationPercentage: 80
+  # targetMemoryUtilizationPercentage: 80
+
+nodeSelector: {}
+
+tolerations: []
+
+affinity: {}
+
+
+rapp:
+  securityEnabled: true 
+  type: invoker 
+  realm: jwt
+  client: jwtprovider-cli
+  roles:
+  - role : provider-viewer
+    grants:
+      - GET
+  apps:
+  - prefix: rapp-jwt-provider
+    methods:
+      - GET
diff --git a/rapps/charts/rapp-jwt-provider/.helmignore b/rapps/charts/rapp-jwt-provider/.helmignore
new file mode 100644
index 0000000..0e8a0eb
--- /dev/null
+++ b/rapps/charts/rapp-jwt-provider/.helmignore
@@ -0,0 +1,23 @@
+# Patterns to ignore when building packages.
+# This supports shell glob matching, relative path matching, and
+# negation (prefixed with !). Only one pattern per line.
+.DS_Store
+# Common VCS dirs
+.git/
+.gitignore
+.bzr/
+.bzrignore
+.hg/
+.hgignore
+.svn/
+# Common backup files
+*.swp
+*.bak
+*.tmp
+*.orig
+*~
+# Various IDEs
+.project
+.idea/
+*.tmproj
+.vscode/
diff --git a/rapps/charts/rapp-jwt-provider/Chart.yaml b/rapps/charts/rapp-jwt-provider/Chart.yaml
new file mode 100644
index 0000000..2d75dd7
--- /dev/null
+++ b/rapps/charts/rapp-jwt-provider/Chart.yaml
@@ -0,0 +1,24 @@
+apiVersion: v2
+name: rapp-jwt-provider
+description: A Helm chart for Kubernetes
+
+# A chart can be either an 'application' or a 'library' chart.
+#
+# Application charts are a collection of templates that can be packaged into versioned archives
+# to be deployed.
+#
+# Library charts provide useful utilities or functions for the chart developer. They're included as
+# a dependency of application charts to inject those utilities and functions into the rendering
+# pipeline. Library charts do not define any templates and therefore cannot be deployed.
+type: application
+
+# This is the chart version. This version number should be incremented each time you make changes
+# to the chart and its templates, including the app version.
+# Versions are expected to follow Semantic Versioning (https://semver.org/)
+version: 0.1.0
+
+# This is the version number of the application being deployed. This version number should be
+# incremented each time you make changes to the application. Versions are not expected to
+# follow Semantic Versioning. They should reflect the version the application is using.
+# It is recommended to use it with quotes.
+appVersion: "1.16.0"
diff --git a/rapps/charts/rapp-jwt-provider/templates/NOTES.txt b/rapps/charts/rapp-jwt-provider/templates/NOTES.txt
new file mode 100644
index 0000000..a244434
--- /dev/null
+++ b/rapps/charts/rapp-jwt-provider/templates/NOTES.txt
@@ -0,0 +1,22 @@
+1. Get the application URL by running these commands:
+{{- if .Values.ingress.enabled }}
+{{- range $host := .Values.ingress.hosts }}
+  {{- range .paths }}
+  http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
+  {{- end }}
+{{- end }}
+{{- else if contains "NodePort" .Values.service.type }}
+  export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "rapp-jwt-provider.fullname" . }})
+  export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
+  echo http://$NODE_IP:$NODE_PORT
+{{- else if contains "LoadBalancer" .Values.service.type }}
+     NOTE: It may take a few minutes for the LoadBalancer IP to be available.
+           You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "rapp-jwt-provider.fullname" . }}'
+  export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "rapp-jwt-provider.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
+  echo http://$SERVICE_IP:{{ .Values.service.port }}
+{{- else if contains "ClusterIP" .Values.service.type }}
+  export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "rapp-jwt-provider.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
+  export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
+  echo "Visit http://127.0.0.1:8080 to use your application"
+  kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
+{{- end }}
diff --git a/rapps/charts/rapp-jwt-provider/templates/_helpers.tpl b/rapps/charts/rapp-jwt-provider/templates/_helpers.tpl
new file mode 100644
index 0000000..46ee122
--- /dev/null
+++ b/rapps/charts/rapp-jwt-provider/templates/_helpers.tpl
@@ -0,0 +1,62 @@
+{{/*
+Expand the name of the chart.
+*/}}
+{{- define "rapp-jwt-provider.name" -}}
+{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Create a default fully qualified app name.
+We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
+If release name contains chart name it will be used as a full name.
+*/}}
+{{- define "rapp-jwt-provider.fullname" -}}
+{{- if .Values.fullnameOverride }}
+{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- $name := default .Chart.Name .Values.nameOverride }}
+{{- if contains $name .Release.Name }}
+{{- .Release.Name | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
+{{- end }}
+{{- end }}
+{{- end }}
+
+{{/*
+Create chart name and version as used by the chart label.
+*/}}
+{{- define "rapp-jwt-provider.chart" -}}
+{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Common labels
+*/}}
+{{- define "rapp-jwt-provider.labels" -}}
+helm.sh/chart: {{ include "rapp-jwt-provider.chart" . }}
+{{ include "rapp-jwt-provider.selectorLabels" . }}
+{{- if .Chart.AppVersion }}
+app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
+{{- end }}
+app.kubernetes.io/managed-by: {{ .Release.Service }}
+{{- end }}
+
+{{/*
+Selector labels
+*/}}
+{{- define "rapp-jwt-provider.selectorLabels" -}}
+app.kubernetes.io/name: {{ include "rapp-jwt-provider.name" . }}
+app.kubernetes.io/instance: {{ .Release.Name }}
+{{- end }}
+
+{{/*
+Create the name of the service account to use
+*/}}
+{{- define "rapp-jwt-provider.serviceAccountName" -}}
+{{- if .Values.serviceAccount.create }}
+{{- default (include "rapp-jwt-provider.fullname" .) .Values.serviceAccount.name }}
+{{- else }}
+{{- default "default" .Values.serviceAccount.name }}
+{{- end }}
+{{- end }}
diff --git a/rapps/charts/rapp-jwt-provider/templates/clusterrole.yaml b/rapps/charts/rapp-jwt-provider/templates/clusterrole.yaml
new file mode 100644
index 0000000..2fab92b
--- /dev/null
+++ b/rapps/charts/rapp-jwt-provider/templates/clusterrole.yaml
@@ -0,0 +1,20 @@
+{{- if .Values.rbac.create -}}
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  labels:
+    app: {{ template "rapp-jwt-provider.name" .}}
+    chart: {{ .Chart.Name }}-{{ .Chart.Version }}
+    heritage: {{ .Release.Service }}
+    release: {{ .Release.Name }}
+  name: {{ template "rapp-jwt-provider.fullname" . }}
+  namespace: {{ .Release.Namespace }} 
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: cluster-admin 
+subjects:
+- kind: ServiceAccount
+  name: {{ template "rapp-jwt-provider.fullname" . }}
+  namespace: {{ .Release.Namespace }}
+{{- end -}}
diff --git a/rapps/charts/rapp-jwt-provider/templates/deployment.yaml b/rapps/charts/rapp-jwt-provider/templates/deployment.yaml
new file mode 100644
index 0000000..67137a3
--- /dev/null
+++ b/rapps/charts/rapp-jwt-provider/templates/deployment.yaml
@@ -0,0 +1,61 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: {{ include "rapp-jwt-provider.fullname" . }}
+  labels:
+    {{- include "rapp-jwt-provider.labels" . | nindent 4 }}
+spec:
+  {{- if not .Values.autoscaling.enabled }}
+  replicas: {{ .Values.replicaCount }}
+  {{- end }}
+  selector:
+    matchLabels:
+      {{- include "rapp-jwt-provider.selectorLabels" . | nindent 6 }}
+  template:
+    metadata:
+      {{- with .Values.podAnnotations }}
+      annotations:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      labels:
+        {{- include "rapp-jwt-provider.selectorLabels" . | nindent 8 }}
+    spec:
+      {{- with .Values.imagePullSecrets }}
+      imagePullSecrets:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      serviceAccountName: {{ include "rapp-jwt-provider.serviceAccountName" . }}
+      securityContext:
+        {{- toYaml .Values.podSecurityContext | nindent 8 }}
+      containers:
+        - name: {{ .Chart.Name }}
+          securityContext:
+            {{- toYaml .Values.securityContext | nindent 12 }}
+          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
+          imagePullPolicy: {{ .Values.image.pullPolicy }}
+          ports:
+            - name: http
+              containerPort: 9000 
+              protocol: TCP
+          livenessProbe:
+            httpGet:
+              path: /
+              port: http
+          readinessProbe:
+            httpGet:
+              path: /
+              port: http
+          resources:
+            {{- toYaml .Values.resources | nindent 12 }}
+      {{- with .Values.nodeSelector }}
+      nodeSelector:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      {{- with .Values.affinity }}
+      affinity:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      {{- with .Values.tolerations }}
+      tolerations:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
diff --git a/rapps/charts/rapp-jwt-provider/templates/hpa.yaml b/rapps/charts/rapp-jwt-provider/templates/hpa.yaml
new file mode 100644
index 0000000..7f4af59
--- /dev/null
+++ b/rapps/charts/rapp-jwt-provider/templates/hpa.yaml
@@ -0,0 +1,28 @@
+{{- if .Values.autoscaling.enabled }}
+apiVersion: autoscaling/v2beta1
+kind: HorizontalPodAutoscaler
+metadata:
+  name: {{ include "rapp-jwt-provider.fullname" . }}
+  labels:
+    {{- include "rapp-jwt-provider.labels" . | nindent 4 }}
+spec:
+  scaleTargetRef:
+    apiVersion: apps/v1
+    kind: Deployment
+    name: {{ include "rapp-jwt-provider.fullname" . }}
+  minReplicas: {{ .Values.autoscaling.minReplicas }}
+  maxReplicas: {{ .Values.autoscaling.maxReplicas }}
+  metrics:
+    {{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
+    - type: Resource
+      resource:
+        name: cpu
+        targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
+    {{- end }}
+    {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
+    - type: Resource
+      resource:
+        name: memory
+        targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
+    {{- end }}
+{{- end }}
diff --git a/rapps/charts/rapp-jwt-provider/templates/ingress.yaml b/rapps/charts/rapp-jwt-provider/templates/ingress.yaml
new file mode 100644
index 0000000..b986148
--- /dev/null
+++ b/rapps/charts/rapp-jwt-provider/templates/ingress.yaml
@@ -0,0 +1,41 @@
+{{- if .Values.ingress.enabled -}}
+{{- $fullName := include "rapp-jwt-provider.fullname" . -}}
+{{- $svcPort := .Values.service.port -}}
+{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
+apiVersion: networking.k8s.io/v1beta1
+{{- else -}}
+apiVersion: extensions/v1beta1
+{{- end }}
+kind: Ingress
+metadata:
+  name: {{ $fullName }}
+  labels:
+    {{- include "rapp-jwt-provider.labels" . | nindent 4 }}
+  {{- with .Values.ingress.annotations }}
+  annotations:
+    {{- toYaml . | nindent 4 }}
+  {{- end }}
+spec:
+  {{- if .Values.ingress.tls }}
+  tls:
+    {{- range .Values.ingress.tls }}
+    - hosts:
+        {{- range .hosts }}
+        - {{ . | quote }}
+        {{- end }}
+      secretName: {{ .secretName }}
+    {{- end }}
+  {{- end }}
+  rules:
+    {{- range .Values.ingress.hosts }}
+    - host: {{ .host | quote }}
+      http:
+        paths:
+          {{- range .paths }}
+          - path: {{ .path }}
+            backend:
+              serviceName: {{ $fullName }}
+              servicePort: {{ $svcPort }}
+          {{- end }}
+    {{- end }}
+  {{- end }}
diff --git a/rapps/charts/rapp-jwt-provider/templates/persistentvolume.yaml b/rapps/charts/rapp-jwt-provider/templates/persistentvolume.yaml
new file mode 100644
index 0000000..c3d23a9
--- /dev/null
+++ b/rapps/charts/rapp-jwt-provider/templates/persistentvolume.yaml
@@ -0,0 +1,16 @@
+apiVersion: v1
+kind: PersistentVolume
+metadata:
+  name: jwt-provider-certs-storage-pv-volume
+  namespace: istio-nonrtric 
+  labels:
+    type: local
+    app: rapp-jwt-provider
+spec:
+  storageClassName: manual
+  capacity:
+    storage: 10Mi
+  accessModes:
+    - ReadOnlyMany 
+  hostPath:
+    path: "/var/rapps/certs"
diff --git a/rapps/charts/rapp-jwt-provider/templates/service.yaml b/rapps/charts/rapp-jwt-provider/templates/service.yaml
new file mode 100644
index 0000000..953ec4f
--- /dev/null
+++ b/rapps/charts/rapp-jwt-provider/templates/service.yaml
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: {{ include "rapp-jwt-provider.fullname" . }}
+  labels:
+    {{- include "rapp-jwt-provider.labels" . | nindent 4 }}
+spec:
+  type: {{ .Values.service.type }}
+  ports:
+    - port: {{ .Values.service.port }}
+      targetPort: http
+      protocol: TCP
+      name: http
+  selector:
+    {{- include "rapp-jwt-provider.selectorLabels" . | nindent 4 }}
diff --git a/rapps/charts/rapp-jwt-provider/templates/serviceaccount.yaml b/rapps/charts/rapp-jwt-provider/templates/serviceaccount.yaml
new file mode 100644
index 0000000..259943a
--- /dev/null
+++ b/rapps/charts/rapp-jwt-provider/templates/serviceaccount.yaml
@@ -0,0 +1,12 @@
+{{- if .Values.serviceAccount.create -}}
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: {{ include "rapp-jwt-provider.serviceAccountName" . }}
+  labels:
+    {{- include "rapp-jwt-provider.labels" . | nindent 4 }}
+  {{- with .Values.serviceAccount.annotations }}
+  annotations:
+    {{- toYaml . | nindent 4 }}
+  {{- end }}
+{{- end }}
diff --git a/rapps/charts/rapp-jwt-provider/templates/tests/test-connection.yaml b/rapps/charts/rapp-jwt-provider/templates/tests/test-connection.yaml
new file mode 100644
index 0000000..6c71552
--- /dev/null
+++ b/rapps/charts/rapp-jwt-provider/templates/tests/test-connection.yaml
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Pod
+metadata:
+  name: "{{ include "rapp-jwt-provider.fullname" . }}-test-connection"
+  labels:
+    {{- include "rapp-jwt-provider.labels" . | nindent 4 }}
+  annotations:
+    "helm.sh/hook": test
+spec:
+  containers:
+    - name: wget
+      image: busybox
+      command: ['wget']
+      args: ['{{ include "rapp-jwt-provider.fullname" . }}:{{ .Values.service.port }}']
+  restartPolicy: Never
diff --git a/rapps/charts/rapp-jwt-provider/values.yaml b/rapps/charts/rapp-jwt-provider/values.yaml
new file mode 100644
index 0000000..ade36e6
--- /dev/null
+++ b/rapps/charts/rapp-jwt-provider/values.yaml
@@ -0,0 +1,104 @@
+# Default values for rapp-jwt-provider.
+# This is a YAML-formatted file.
+# Declare variables to be passed into your templates.
+
+replicaCount: 1
+
+image:
+  repository: ktimoney/rapps-rapp-jwt-provider
+  pullPolicy: IfNotPresent
+  # Overrides the image tag whose default is the chart appVersion.
+  tag: "latest"
+
+imagePullSecrets: []
+nameOverride: ""
+fullnameOverride: ""
+
+serviceAccount:
+  # Specifies whether a service account should be created
+  create: true 
+  # Annotations to add to the service account
+  annotations: {}
+  # The name of the service account to use.
+  # If not set and create is true, a name is generated using the fullname template
+  name: "rapp-jwt-provider"
+
+rbac:
+  # Specifies whether rbac should be enabled 
+  create: true 
+
+podAnnotations: {}
+
+podSecurityContext: {}
+  # fsGroup: 2000
+
+securityContext: {}
+  # capabilities:
+  #   drop:
+  #   - ALL
+  # readOnlyRootFilesystem: true
+  # runAsNonRoot: true
+  # runAsUser: 1000
+
+service:
+  type: ClusterIP 
+  port: 80
+
+ingress:
+  enabled: false
+  annotations: {}
+    # kubernetes.io/ingress.class: nginx
+    # kubernetes.io/tls-acme: "true"
+  hosts:
+    - host: rapp-jwt-provider 
+      paths:
+      - path: /
+        backend:
+          serviceName: rapp-jwt-provider 
+          servicePort: 80
+  tls: []
+  #  - secretName: chart-example-tls
+  #    hosts:
+  #      - chart-example.local
+
+resources: {}
+  # We usually recommend not to specify default resources and to leave this as a conscious
+  # choice for the user. This also increases chances charts run on environments with little
+  # resources, such as Minikube. If you do want to specify resources, uncomment the following
+  # lines, adjust them as necessary, and remove the curly braces after 'resources:'.
+  # limits:
+  #   cpu: 100m
+  #   memory: 128Mi
+  # requests:
+  #   cpu: 100m
+  #   memory: 128Mi
+
+autoscaling:
+  enabled: false
+  minReplicas: 1
+  maxReplicas: 100
+  targetCPUUtilizationPercentage: 80
+  # targetMemoryUtilizationPercentage: 80
+
+nodeSelector: {}
+
+tolerations: []
+
+affinity: {}
+
+rapp:
+  securityEnabled: true 
+  type: provider
+  realm: jwt
+  client: jwtprovider-cli 
+  roles: 
+  - role : provider-viewer
+    grants: 
+      - GET
+  - role : provider-admin
+    grants: 
+      - GET
+      - PUT
+      - PUT
+      - DELETE 
+
diff --git a/rapps/charts/rapp-x509-invoker/.helmignore b/rapps/charts/rapp-x509-invoker/.helmignore
new file mode 100644
index 0000000..0e8a0eb
--- /dev/null
+++ b/rapps/charts/rapp-x509-invoker/.helmignore
@@ -0,0 +1,23 @@
+# Patterns to ignore when building packages.
+# This supports shell glob matching, relative path matching, and
+# negation (prefixed with !). Only one pattern per line.
+.DS_Store
+# Common VCS dirs
+.git/
+.gitignore
+.bzr/
+.bzrignore
+.hg/
+.hgignore
+.svn/
+# Common backup files
+*.swp
+*.bak
+*.tmp
+*.orig
+*~
+# Various IDEs
+.project
+.idea/
+*.tmproj
+.vscode/
diff --git a/rapps/charts/rapp-x509-invoker/Chart.yaml b/rapps/charts/rapp-x509-invoker/Chart.yaml
new file mode 100644
index 0000000..0018754
--- /dev/null
+++ b/rapps/charts/rapp-x509-invoker/Chart.yaml
@@ -0,0 +1,24 @@
+apiVersion: v2
+name: rapp-x509-invoker
+description: A Helm chart for Kubernetes
+
+# A chart can be either an 'application' or a 'library' chart.
+#
+# Application charts are a collection of templates that can be packaged into versioned archives
+# to be deployed.
+#
+# Library charts provide useful utilities or functions for the chart developer. They're included as
+# a dependency of application charts to inject those utilities and functions into the rendering
+# pipeline. Library charts do not define any templates and therefore cannot be deployed.
+type: application
+
+# This is the chart version. This version number should be incremented each time you make changes
+# to the chart and its templates, including the app version.
+# Versions are expected to follow Semantic Versioning (https://semver.org/)
+version: 0.1.0
+
+# This is the version number of the application being deployed. This version number should be
+# incremented each time you make changes to the application. Versions are not expected to
+# follow Semantic Versioning. They should reflect the version the application is using.
+# It is recommended to use it with quotes.
+appVersion: "1.16.0"
diff --git a/rapps/charts/rapp-x509-invoker/templates/NOTES.txt b/rapps/charts/rapp-x509-invoker/templates/NOTES.txt
new file mode 100644
index 0000000..200f372
--- /dev/null
+++ b/rapps/charts/rapp-x509-invoker/templates/NOTES.txt
@@ -0,0 +1,22 @@
+1. Get the application URL by running these commands:
+{{- if .Values.ingress.enabled }}
+{{- range $host := .Values.ingress.hosts }}
+  {{- range .paths }}
+  http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
+  {{- end }}
+{{- end }}
+{{- else if contains "NodePort" .Values.service.type }}
+  export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "rapp-x509-invoker.fullname" . }})
+  export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
+  echo http://$NODE_IP:$NODE_PORT
+{{- else if contains "LoadBalancer" .Values.service.type }}
+     NOTE: It may take a few minutes for the LoadBalancer IP to be available.
+           You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "rapp-x509-invoker.fullname" . }}'
+  export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "rapp-x509-invoker.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
+  echo http://$SERVICE_IP:{{ .Values.service.port }}
+{{- else if contains "ClusterIP" .Values.service.type }}
+  export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "rapp-x509-invoker.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
+  export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
+  echo "Visit http://127.0.0.1:8080 to use your application"
+  kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
+{{- end }}
diff --git a/rapps/charts/rapp-x509-invoker/templates/_helpers.tpl b/rapps/charts/rapp-x509-invoker/templates/_helpers.tpl
new file mode 100644
index 0000000..b8a93da
--- /dev/null
+++ b/rapps/charts/rapp-x509-invoker/templates/_helpers.tpl
@@ -0,0 +1,62 @@
+{{/*
+Expand the name of the chart.
+*/}}
+{{- define "rapp-x509-invoker.name" -}}
+{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Create a default fully qualified app name.
+We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
+If release name contains chart name it will be used as a full name.
+*/}}
+{{- define "rapp-x509-invoker.fullname" -}}
+{{- if .Values.fullnameOverride }}
+{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- $name := default .Chart.Name .Values.nameOverride }}
+{{- if contains $name .Release.Name }}
+{{- .Release.Name | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
+{{- end }}
+{{- end }}
+{{- end }}
+
+{{/*
+Create chart name and version as used by the chart label.
+*/}}
+{{- define "rapp-x509-invoker.chart" -}}
+{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Common labels
+*/}}
+{{- define "rapp-x509-invoker.labels" -}}
+helm.sh/chart: {{ include "rapp-x509-invoker.chart" . }}
+{{ include "rapp-x509-invoker.selectorLabels" . }}
+{{- if .Chart.AppVersion }}
+app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
+{{- end }}
+app.kubernetes.io/managed-by: {{ .Release.Service }}
+{{- end }}
+
+{{/*
+Selector labels
+*/}}
+{{- define "rapp-x509-invoker.selectorLabels" -}}
+app.kubernetes.io/name: {{ include "rapp-x509-invoker.name" . }}
+app.kubernetes.io/instance: {{ .Release.Name }}
+{{- end }}
+
+{{/*
+Create the name of the service account to use
+*/}}
+{{- define "rapp-x509-invoker.serviceAccountName" -}}
+{{- if .Values.serviceAccount.create }}
+{{- default (include "rapp-x509-invoker.fullname" .) .Values.serviceAccount.name }}
+{{- else }}
+{{- default "default" .Values.serviceAccount.name }}
+{{- end }}
+{{- end }}
diff --git a/rapps/charts/rapp-x509-invoker/templates/clusterrole.yaml b/rapps/charts/rapp-x509-invoker/templates/clusterrole.yaml
new file mode 100644
index 0000000..71eddc2
--- /dev/null
+++ b/rapps/charts/rapp-x509-invoker/templates/clusterrole.yaml
@@ -0,0 +1,20 @@
+{{- if .Values.rbac.create -}}
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  labels:
+    app: {{ template "rapp-x509-invoker.name" .}}
+    chart: {{ .Chart.Name }}-{{ .Chart.Version }}
+    heritage: {{ .Release.Service }}
+    release: {{ .Release.Name }}
+  name: {{ template "rapp-x509-invoker.fullname" . }}
+  namespace: {{ .Release.Namespace }} 
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: cluster-admin 
+subjects:
+- kind: ServiceAccount
+  name: {{ template "rapp-x509-invoker.fullname" . }}
+  namespace: {{ .Release.Namespace }}
+{{- end -}}
diff --git a/rapps/charts/rapp-x509-invoker/templates/deployment.yaml b/rapps/charts/rapp-x509-invoker/templates/deployment.yaml
new file mode 100644
index 0000000..d1fca53
--- /dev/null
+++ b/rapps/charts/rapp-x509-invoker/templates/deployment.yaml
@@ -0,0 +1,78 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: {{ include "rapp-x509-invoker.fullname" . }}
+  labels:
+    {{- include "rapp-x509-invoker.labels" . | nindent 4 }}
+spec:
+  {{- if not .Values.autoscaling.enabled }}
+  replicas: {{ .Values.replicaCount }}
+  {{- end }}
+  selector:
+    matchLabels:
+      {{- include "rapp-x509-invoker.selectorLabels" . | nindent 6 }}
+  template:
+    metadata:
+      {{- with .Values.podAnnotations }}
+      annotations:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      labels:
+        {{- include "rapp-x509-invoker.selectorLabels" . | nindent 8 }}
+    spec:
+      {{- with .Values.imagePullSecrets }}
+      imagePullSecrets:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      serviceAccountName: {{ include "rapp-x509-invoker.serviceAccountName" . }}
+      securityContext:
+        {{- toYaml .Values.podSecurityContext | nindent 8 }}
+      containers:
+        - name: {{ .Chart.Name }}
+          securityContext:
+            {{- toYaml .Values.securityContext | nindent 12 }}
+          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
+          imagePullPolicy: {{ .Values.image.pullPolicy }}
+          command: ["/app/rapps-rapp-x509-invoker"]
+          args: [
+                  "-securityEnabled", "{{ .Values.rapp.securityEnabled }}",
+                  "-realm", "{{ .Values.rapp.realm }}",
+                  "-client", "{{ .Values.rapp.client }}",
+                  "-role",  "{{ with index .Values.rapp.roles 0 }}{{ .role }}{{ end }}",
+                  "-rapp", "{{ with index .Values.rapp.apps 0 }}{{ .prefix }}{{ end }}",
+                  "-methods", "{{- range .Values.rapp.apps }}{{ join "," .methods }}{{- end }}"
+                ]
+          ports:
+            - name: http
+              containerPort: 9000
+              protocol: TCP
+          livenessProbe:
+            httpGet:
+              path: /health
+              port: 9000 
+            initialDelaySeconds: 5 
+            periodSeconds: 60
+          readinessProbe:
+              exec:
+                command: ["stat", "init.txt"]
+          resources:
+            {{- toYaml .Values.resources | nindent 12 }}
+          volumeMounts:
+          - name: x509-rapps-certs-persistent-storage
+            mountPath: /certs
+      volumes:
+      - name: x509-rapps-certs-persistent-storage
+        persistentVolumeClaim:
+          claimName: x509-rapps-certs-storage-pv-claim
+      {{- with .Values.nodeSelector }}
+      nodeSelector:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      {{- with .Values.affinity }}
+      affinity:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      {{- with .Values.tolerations }}
+      tolerations:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
diff --git a/rapps/charts/rapp-x509-invoker/templates/hpa.yaml b/rapps/charts/rapp-x509-invoker/templates/hpa.yaml
new file mode 100644
index 0000000..b86fa99
--- /dev/null
+++ b/rapps/charts/rapp-x509-invoker/templates/hpa.yaml
@@ -0,0 +1,28 @@
+{{- if .Values.autoscaling.enabled }}
+apiVersion: autoscaling/v2beta1
+kind: HorizontalPodAutoscaler
+metadata:
+  name: {{ include "rapp-x509-invoker.fullname" . }}
+  labels:
+    {{- include "rapp-x509-invoker.labels" . | nindent 4 }}
+spec:
+  scaleTargetRef:
+    apiVersion: apps/v1
+    kind: Deployment
+    name: {{ include "rapp-x509-invoker.fullname" . }}
+  minReplicas: {{ .Values.autoscaling.minReplicas }}
+  maxReplicas: {{ .Values.autoscaling.maxReplicas }}
+  metrics:
+    {{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
+    - type: Resource
+      resource:
+        name: cpu
+        targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
+    {{- end }}
+    {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
+    - type: Resource
+      resource:
+        name: memory
+        targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
+    {{- end }}
+{{- end }}
diff --git a/rapps/charts/rapp-x509-invoker/templates/ingress.yaml b/rapps/charts/rapp-x509-invoker/templates/ingress.yaml
new file mode 100644
index 0000000..a49f3a3
--- /dev/null
+++ b/rapps/charts/rapp-x509-invoker/templates/ingress.yaml
@@ -0,0 +1,41 @@
+{{- if .Values.ingress.enabled -}}
+{{- $fullName := include "rapp-x509-invoker.fullname" . -}}
+{{- $svcPort := .Values.service.port -}}
+{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
+apiVersion: networking.k8s.io/v1beta1
+{{- else -}}
+apiVersion: extensions/v1beta1
+{{- end }}
+kind: Ingress
+metadata:
+  name: {{ $fullName }}
+  labels:
+    {{- include "rapp-x509-invoker.labels" . | nindent 4 }}
+  {{- with .Values.ingress.annotations }}
+  annotations:
+    {{- toYaml . | nindent 4 }}
+  {{- end }}
+spec:
+  {{- if .Values.ingress.tls }}
+  tls:
+    {{- range .Values.ingress.tls }}
+    - hosts:
+        {{- range .hosts }}
+        - {{ . | quote }}
+        {{- end }}
+      secretName: {{ .secretName }}
+    {{- end }}
+  {{- end }}
+  rules:
+    {{- range .Values.ingress.hosts }}
+    - host: {{ .host | quote }}
+      http:
+        paths:
+          {{- range .paths }}
+          - path: {{ .path }}
+            backend:
+              serviceName: {{ $fullName }}
+              servicePort: {{ $svcPort }}
+          {{- end }}
+    {{- end }}
+  {{- end }}
diff --git a/rapps/charts/rapp-x509-invoker/templates/persistentvolume.yaml b/rapps/charts/rapp-x509-invoker/templates/persistentvolume.yaml
new file mode 100644
index 0000000..b64a1a8
--- /dev/null
+++ b/rapps/charts/rapp-x509-invoker/templates/persistentvolume.yaml
@@ -0,0 +1,16 @@
+apiVersion: v1
+kind: PersistentVolume
+metadata:
+  name: x509-rapps-certs-storage-pv-volume
+  namespace: istio-nonrtric 
+  labels:
+    type: local
+    app: rapp-x509-invoker 
+spec:
+  storageClassName: manual
+  capacity:
+    storage: 10Mi
+  accessModes:
+    - ReadOnlyMany 
+  hostPath:
+    path: "/var/rapps/certs"
diff --git a/rapps/charts/rapp-x509-invoker/templates/persistentvolumeclaim.yaml b/rapps/charts/rapp-x509-invoker/templates/persistentvolumeclaim.yaml
new file mode 100644
index 0000000..1622ed5
--- /dev/null
+++ b/rapps/charts/rapp-x509-invoker/templates/persistentvolumeclaim.yaml
@@ -0,0 +1,14 @@
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  name: x509-rapps-certs-storage-pv-claim
+  namespace: istio-nonrtric 
+  labels:
+    app: rapp-x509-invoker 
+spec:
+  storageClassName: manual
+  accessModes:
+    - ReadOnlyMany 
+  resources:
+    requests:
+      storage: 10Mi
diff --git a/rapps/charts/rapp-x509-invoker/templates/service.yaml b/rapps/charts/rapp-x509-invoker/templates/service.yaml
new file mode 100644
index 0000000..e363a2f
--- /dev/null
+++ b/rapps/charts/rapp-x509-invoker/templates/service.yaml
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: {{ include "rapp-x509-invoker.fullname" . }}
+  labels:
+    {{- include "rapp-x509-invoker.labels" . | nindent 4 }}
+spec:
+  type: {{ .Values.service.type }}
+  ports:
+    - port: {{ .Values.service.port }}
+      targetPort: http
+      protocol: TCP
+      name: http
+  selector:
+    {{- include "rapp-x509-invoker.selectorLabels" . | nindent 4 }}
diff --git a/rapps/charts/rapp-x509-invoker/templates/serviceaccount.yaml b/rapps/charts/rapp-x509-invoker/templates/serviceaccount.yaml
new file mode 100644
index 0000000..4c3405c
--- /dev/null
+++ b/rapps/charts/rapp-x509-invoker/templates/serviceaccount.yaml
@@ -0,0 +1,12 @@
+{{- if .Values.serviceAccount.create -}}
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: {{ include "rapp-x509-invoker.serviceAccountName" . }}
+  labels:
+    {{- include "rapp-x509-invoker.labels" . | nindent 4 }}
+  {{- with .Values.serviceAccount.annotations }}
+  annotations:
+    {{- toYaml . | nindent 4 }}
+  {{- end }}
+{{- end }}
diff --git a/rapps/charts/rapp-x509-invoker/templates/tests/test-connection.yaml b/rapps/charts/rapp-x509-invoker/templates/tests/test-connection.yaml
new file mode 100644
index 0000000..ad4855a
--- /dev/null
+++ b/rapps/charts/rapp-x509-invoker/templates/tests/test-connection.yaml
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Pod
+metadata:
+  name: "{{ include "rapp-x509-invoker.fullname" . }}-test-connection"
+  labels:
+    {{- include "rapp-x509-invoker.labels" . | nindent 4 }}
+  annotations:
+    "helm.sh/hook": test
+spec:
+  containers:
+    - name: wget
+      image: busybox
+      command: ['wget']
+      args: ['{{ include "rapp-x509-invoker.fullname" . }}:{{ .Values.service.port }}']
+  restartPolicy: Never
diff --git a/rapps/charts/rapp-x509-invoker/values.yaml b/rapps/charts/rapp-x509-invoker/values.yaml
new file mode 100644
index 0000000..bcdfaed
--- /dev/null
+++ b/rapps/charts/rapp-x509-invoker/values.yaml
@@ -0,0 +1,102 @@
+# Default values for rapp-x509-invoker.
+# This is a YAML-formatted file.
+# Declare variables to be passed into your templates.
+
+replicaCount: 1
+
+image:
+  repository: ktimoney/rapps-rapp-x509-invoker
+  pullPolicy: IfNotPresent
+  # Overrides the image tag whose default is the chart appVersion.
+  tag: "latest"
+
+imagePullSecrets: []
+nameOverride: ""
+fullnameOverride: ""
+
+serviceAccount:
+  # Specifies whether a service account should be created
+  create: true 
+  # Annotations to add to the service account
+  annotations: {}
+  # The name of the service account to use.
+  # If not set and create is true, a name is generated using the fullname template
+  name: "rapp-x509-invoker"
+
+rbac:
+  # Specifies whether rbac is enabled 
+  create: true 
+
+podAnnotations: {}
+
+podSecurityContext: {}
+  # fsGroup: 2000
+
+securityContext: {}
+  # capabilities:
+  #   drop:
+  #   - ALL
+  # readOnlyRootFilesystem: true
+  # runAsNonRoot: true
+  # runAsUser: 1000
+
+service:
+  type: ClusterIP 
+  port: 80
+
+ingress:
+  enabled: false
+  annotations: {}
+    # kubernetes.io/ingress.class: nginx
+    # kubernetes.io/tls-acme: "true"
+  hosts:
+    - host: rapp-x509-invoker
+      paths:
+      - path: /
+        backend:
+          serviceName: rapp-x509-invoker
+          servicePort: 80
+  tls: []
+  #  - secretName: chart-example-tls
+  #    hosts:
+  #      - chart-example.local
+
+resources: {}
+  # We usually recommend not to specify default resources and to leave this as a conscious
+  # choice for the user. This also increases chances charts run on environments with little
+  # resources, such as Minikube. If you do want to specify resources, uncomment the following
+  # lines, adjust them as necessary, and remove the curly braces after 'resources:'.
+  # limits:
+  #   cpu: 100m
+  #   memory: 128Mi
+  # requests:
+  #   cpu: 100m
+  #   memory: 128Mi
+
+autoscaling:
+  enabled: false
+  minReplicas: 1
+  maxReplicas: 100
+  targetCPUUtilizationPercentage: 80
+  # targetMemoryUtilizationPercentage: 80
+
+nodeSelector: {}
+
+tolerations: []
+
+affinity: {}
+
+
+rapp:
+  securityEnabled: true 
+  type: invoker 
+  realm: x509
+  client: x509provider-cli
+  roles:
+  - role : provider-viewer
+    grants:
+      - GET
+  apps:
+  - prefix: rapp-x509-provider
+    methods:
+      - GET
diff --git a/rapps/charts/rapp-x509-provider/.helmignore b/rapps/charts/rapp-x509-provider/.helmignore
new file mode 100644
index 0000000..0e8a0eb
--- /dev/null
+++ b/rapps/charts/rapp-x509-provider/.helmignore
@@ -0,0 +1,23 @@
+# Patterns to ignore when building packages.
+# This supports shell glob matching, relative path matching, and
+# negation (prefixed with !). Only one pattern per line.
+.DS_Store
+# Common VCS dirs
+.git/
+.gitignore
+.bzr/
+.bzrignore
+.hg/
+.hgignore
+.svn/
+# Common backup files
+*.swp
+*.bak
+*.tmp
+*.orig
+*~
+# Various IDEs
+.project
+.idea/
+*.tmproj
+.vscode/
diff --git a/rapps/charts/rapp-x509-provider/Chart.yaml b/rapps/charts/rapp-x509-provider/Chart.yaml
new file mode 100644
index 0000000..0148f85
--- /dev/null
+++ b/rapps/charts/rapp-x509-provider/Chart.yaml
@@ -0,0 +1,24 @@
+apiVersion: v2
+name: rapp-x509-provider
+description: A Helm chart for Kubernetes
+
+# A chart can be either an 'application' or a 'library' chart.
+#
+# Application charts are a collection of templates that can be packaged into versioned archives
+# to be deployed.
+#
+# Library charts provide useful utilities or functions for the chart developer. They're included as
+# a dependency of application charts to inject those utilities and functions into the rendering
+# pipeline. Library charts do not define any templates and therefore cannot be deployed.
+type: application
+
+# This is the chart version. This version number should be incremented each time you make changes
+# to the chart and its templates, including the app version.
+# Versions are expected to follow Semantic Versioning (https://semver.org/)
+version: 0.1.0
+
+# This is the version number of the application being deployed. This version number should be
+# incremented each time you make changes to the application. Versions are not expected to
+# follow Semantic Versioning. They should reflect the version the application is using.
+# It is recommended to use it with quotes.
+appVersion: "1.16.0"
diff --git a/rapps/charts/rapp-x509-provider/templates/NOTES.txt b/rapps/charts/rapp-x509-provider/templates/NOTES.txt
new file mode 100644
index 0000000..3f038ee
--- /dev/null
+++ b/rapps/charts/rapp-x509-provider/templates/NOTES.txt
@@ -0,0 +1,22 @@
+1. Get the application URL by running these commands:
+{{- if .Values.ingress.enabled }}
+{{- range $host := .Values.ingress.hosts }}
+  {{- range .paths }}
+  http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
+  {{- end }}
+{{- end }}
+{{- else if contains "NodePort" .Values.service.type }}
+  export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "rapp-x509-provider.fullname" . }})
+  export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
+  echo http://$NODE_IP:$NODE_PORT
+{{- else if contains "LoadBalancer" .Values.service.type }}
+     NOTE: It may take a few minutes for the LoadBalancer IP to be available.
+           You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "rapp-x509-provider.fullname" . }}'
+  export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "rapp-x509-provider.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
+  echo http://$SERVICE_IP:{{ .Values.service.port }}
+{{- else if contains "ClusterIP" .Values.service.type }}
+  export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "rapp-x509-provider.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
+  export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
+  echo "Visit http://127.0.0.1:8080 to use your application"
+  kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
+{{- end }}
diff --git a/rapps/charts/rapp-x509-provider/templates/_helpers.tpl b/rapps/charts/rapp-x509-provider/templates/_helpers.tpl
new file mode 100644
index 0000000..a2e7b7e
--- /dev/null
+++ b/rapps/charts/rapp-x509-provider/templates/_helpers.tpl
@@ -0,0 +1,62 @@
+{{/*
+Expand the name of the chart.
+*/}}
+{{- define "rapp-x509-provider.name" -}}
+{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Create a default fully qualified app name.
+We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
+If release name contains chart name it will be used as a full name.
+*/}}
+{{- define "rapp-x509-provider.fullname" -}}
+{{- if .Values.fullnameOverride }}
+{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- $name := default .Chart.Name .Values.nameOverride }}
+{{- if contains $name .Release.Name }}
+{{- .Release.Name | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
+{{- end }}
+{{- end }}
+{{- end }}
+
+{{/*
+Create chart name and version as used by the chart label.
+*/}}
+{{- define "rapp-x509-provider.chart" -}}
+{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Common labels
+*/}}
+{{- define "rapp-x509-provider.labels" -}}
+helm.sh/chart: {{ include "rapp-x509-provider.chart" . }}
+{{ include "rapp-x509-provider.selectorLabels" . }}
+{{- if .Chart.AppVersion }}
+app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
+{{- end }}
+app.kubernetes.io/managed-by: {{ .Release.Service }}
+{{- end }}
+
+{{/*
+Selector labels
+*/}}
+{{- define "rapp-x509-provider.selectorLabels" -}}
+app.kubernetes.io/name: {{ include "rapp-x509-provider.name" . }}
+app.kubernetes.io/instance: {{ .Release.Name }}
+{{- end }}
+
+{{/*
+Create the name of the service account to use
+*/}}
+{{- define "rapp-x509-provider.serviceAccountName" -}}
+{{- if .Values.serviceAccount.create }}
+{{- default (include "rapp-x509-provider.fullname" .) .Values.serviceAccount.name }}
+{{- else }}
+{{- default "default" .Values.serviceAccount.name }}
+{{- end }}
+{{- end }}
diff --git a/rapps/charts/rapp-x509-provider/templates/clusterrole.yaml b/rapps/charts/rapp-x509-provider/templates/clusterrole.yaml
new file mode 100644
index 0000000..2747446
--- /dev/null
+++ b/rapps/charts/rapp-x509-provider/templates/clusterrole.yaml
@@ -0,0 +1,20 @@
+{{- if .Values.rbac.create -}}
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  labels:
+    app: {{ template "rapp-x509-provider.name" .}}
+    chart: {{ .Chart.Name }}-{{ .Chart.Version }}
+    heritage: {{ .Release.Service }}
+    release: {{ .Release.Name }}
+  name: {{ template "rapp-x509-provider.fullname" . }}
+  namespace: {{ .Release.Namespace }} 
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: cluster-admin 
+subjects:
+- kind: ServiceAccount
+  name: {{ template "rapp-x509-provider.fullname" . }}
+  namespace: {{ .Release.Namespace }}
+{{- end -}}
diff --git a/rapps/charts/rapp-x509-provider/templates/deployment.yaml b/rapps/charts/rapp-x509-provider/templates/deployment.yaml
new file mode 100644
index 0000000..48b8235
--- /dev/null
+++ b/rapps/charts/rapp-x509-provider/templates/deployment.yaml
@@ -0,0 +1,61 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: {{ include "rapp-x509-provider.fullname" . }}
+  labels:
+    {{- include "rapp-x509-provider.labels" . | nindent 4 }}
+spec:
+  {{- if not .Values.autoscaling.enabled }}
+  replicas: {{ .Values.replicaCount }}
+  {{- end }}
+  selector:
+    matchLabels:
+      {{- include "rapp-x509-provider.selectorLabels" . | nindent 6 }}
+  template:
+    metadata:
+      {{- with .Values.podAnnotations }}
+      annotations:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      labels:
+        {{- include "rapp-x509-provider.selectorLabels" . | nindent 8 }}
+    spec:
+      {{- with .Values.imagePullSecrets }}
+      imagePullSecrets:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      serviceAccountName: {{ include "rapp-x509-provider.serviceAccountName" . }}
+      securityContext:
+        {{- toYaml .Values.podSecurityContext | nindent 8 }}
+      containers:
+        - name: {{ .Chart.Name }}
+          securityContext:
+            {{- toYaml .Values.securityContext | nindent 12 }}
+          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
+          imagePullPolicy: {{ .Values.image.pullPolicy }}
+          ports:
+            - name: http
+              containerPort: 9000 
+              protocol: TCP
+          livenessProbe:
+            httpGet:
+              path: /
+              port: http
+          readinessProbe:
+            httpGet:
+              path: /
+              port: http
+          resources:
+            {{- toYaml .Values.resources | nindent 12 }}
+      {{- with .Values.nodeSelector }}
+      nodeSelector:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      {{- with .Values.affinity }}
+      affinity:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      {{- with .Values.tolerations }}
+      tolerations:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
diff --git a/rapps/charts/rapp-x509-provider/templates/hpa.yaml b/rapps/charts/rapp-x509-provider/templates/hpa.yaml
new file mode 100644
index 0000000..a716621
--- /dev/null
+++ b/rapps/charts/rapp-x509-provider/templates/hpa.yaml
@@ -0,0 +1,28 @@
+{{- if .Values.autoscaling.enabled }}
+apiVersion: autoscaling/v2beta1
+kind: HorizontalPodAutoscaler
+metadata:
+  name: {{ include "rapp-x509-provider.fullname" . }}
+  labels:
+    {{- include "rapp-x509-provider.labels" . | nindent 4 }}
+spec:
+  scaleTargetRef:
+    apiVersion: apps/v1
+    kind: Deployment
+    name: {{ include "rapp-x509-provider.fullname" . }}
+  minReplicas: {{ .Values.autoscaling.minReplicas }}
+  maxReplicas: {{ .Values.autoscaling.maxReplicas }}
+  metrics:
+    {{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
+    - type: Resource
+      resource:
+        name: cpu
+        targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
+    {{- end }}
+    {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
+    - type: Resource
+      resource:
+        name: memory
+        targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
+    {{- end }}
+{{- end }}
diff --git a/rapps/charts/rapp-x509-provider/templates/ingress.yaml b/rapps/charts/rapp-x509-provider/templates/ingress.yaml
new file mode 100644
index 0000000..da8a2e9
--- /dev/null
+++ b/rapps/charts/rapp-x509-provider/templates/ingress.yaml
@@ -0,0 +1,41 @@
+{{- if .Values.ingress.enabled -}}
+{{- $fullName := include "rapp-x509-provider.fullname" . -}}
+{{- $svcPort := .Values.service.port -}}
+{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
+apiVersion: networking.k8s.io/v1beta1
+{{- else -}}
+apiVersion: extensions/v1beta1
+{{- end }}
+kind: Ingress
+metadata:
+  name: {{ $fullName }}
+  labels:
+    {{- include "rapp-x509-provider.labels" . | nindent 4 }}
+  {{- with .Values.ingress.annotations }}
+  annotations:
+    {{- toYaml . | nindent 4 }}
+  {{- end }}
+spec:
+  {{- if .Values.ingress.tls }}
+  tls:
+    {{- range .Values.ingress.tls }}
+    - hosts:
+        {{- range .hosts }}
+        - {{ . | quote }}
+        {{- end }}
+      secretName: {{ .secretName }}
+    {{- end }}
+  {{- end }}
+  rules:
+    {{- range .Values.ingress.hosts }}
+    - host: {{ .host | quote }}
+      http:
+        paths:
+          {{- range .paths }}
+          - path: {{ .path }}
+            backend:
+              serviceName: {{ $fullName }}
+              servicePort: {{ $svcPort }}
+          {{- end }}
+    {{- end }}
+  {{- end }}
diff --git a/rapps/charts/rapp-x509-provider/templates/service.yaml b/rapps/charts/rapp-x509-provider/templates/service.yaml
new file mode 100644
index 0000000..8e21778
--- /dev/null
+++ b/rapps/charts/rapp-x509-provider/templates/service.yaml
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: {{ include "rapp-x509-provider.fullname" . }}
+  labels:
+    {{- include "rapp-x509-provider.labels" . | nindent 4 }}
+spec:
+  type: {{ .Values.service.type }}
+  ports:
+    - port: {{ .Values.service.port }}
+      targetPort: http
+      protocol: TCP
+      name: http
+  selector:
+    {{- include "rapp-x509-provider.selectorLabels" . | nindent 4 }}
diff --git a/rapps/charts/rapp-x509-provider/templates/serviceaccount.yaml b/rapps/charts/rapp-x509-provider/templates/serviceaccount.yaml
new file mode 100644
index 0000000..eb675ae
--- /dev/null
+++ b/rapps/charts/rapp-x509-provider/templates/serviceaccount.yaml
@@ -0,0 +1,12 @@
+{{- if .Values.serviceAccount.create -}}
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: {{ include "rapp-x509-provider.serviceAccountName" . }}
+  labels:
+    {{- include "rapp-x509-provider.labels" . | nindent 4 }}
+  {{- with .Values.serviceAccount.annotations }}
+  annotations:
+    {{- toYaml . | nindent 4 }}
+  {{- end }}
+{{- end }}
diff --git a/rapps/charts/rapp-x509-provider/templates/tests/test-connection.yaml b/rapps/charts/rapp-x509-provider/templates/tests/test-connection.yaml
new file mode 100644
index 0000000..6549eb1
--- /dev/null
+++ b/rapps/charts/rapp-x509-provider/templates/tests/test-connection.yaml
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Pod
+metadata:
+  name: "{{ include "rapp-x509-provider.fullname" . }}-test-connection"
+  labels:
+    {{- include "rapp-x509-provider.labels" . | nindent 4 }}
+  annotations:
+    "helm.sh/hook": test
+spec:
+  containers:
+    - name: wget
+      image: busybox
+      command: ['wget']
+      args: ['{{ include "rapp-x509-provider.fullname" . }}:{{ .Values.service.port }}']
+  restartPolicy: Never
diff --git a/rapps/charts/rapp-x509-provider/values.yaml b/rapps/charts/rapp-x509-provider/values.yaml
new file mode 100644
index 0000000..ba6d7be
--- /dev/null
+++ b/rapps/charts/rapp-x509-provider/values.yaml
@@ -0,0 +1,104 @@
+# Default values for rapp-x509-provider.
+# This is a YAML-formatted file.
+# Declare variables to be passed into your templates.
+
+replicaCount: 1
+
+image:
+  repository: ktimoney/rapps-rapp-x509-provider
+  pullPolicy: IfNotPresent
+  # Overrides the image tag whose default is the chart appVersion.
+  tag: "latest"
+
+imagePullSecrets: []
+nameOverride: ""
+fullnameOverride: ""
+
+serviceAccount:
+  # Specifies whether a service account should be created
+  create: true 
+  # Annotations to add to the service account
+  annotations: {}
+  # The name of the service account to use.
+  # If not set and create is true, a name is generated using the fullname template
+  name: "rapp-x509-provider"
+
+rbac:
+  # Specifies whether rbac should be enabled 
+  create: true 
+
+podAnnotations: {}
+
+podSecurityContext: {}
+  # fsGroup: 2000
+
+securityContext: {}
+  # capabilities:
+  #   drop:
+  #   - ALL
+  # readOnlyRootFilesystem: true
+  # runAsNonRoot: true
+  # runAsUser: 1000
+
+service:
+  type: ClusterIP 
+  port: 80
+
+ingress:
+  enabled: false
+  annotations: {}
+    # kubernetes.io/ingress.class: nginx
+    # kubernetes.io/tls-acme: "true"
+  hosts:
+    - host: rapp-x509-provider 
+      paths:
+      - path: /
+        backend:
+          serviceName: rapp-x509-provider 
+          servicePort: 80
+  tls: []
+  #  - secretName: chart-example-tls
+  #    hosts:
+  #      - chart-example.local
+
+resources: {}
+  # We usually recommend not to specify default resources and to leave this as a conscious
+  # choice for the user. This also increases chances charts run on environments with little
+  # resources, such as Minikube. If you do want to specify resources, uncomment the following
+  # lines, adjust them as necessary, and remove the curly braces after 'resources:'.
+  # limits:
+  #   cpu: 100m
+  #   memory: 128Mi
+  # requests:
+  #   cpu: 100m
+  #   memory: 128Mi
+
+autoscaling:
+  enabled: false
+  minReplicas: 1
+  maxReplicas: 100
+  targetCPUUtilizationPercentage: 80
+  # targetMemoryUtilizationPercentage: 80
+
+nodeSelector: {}
+
+tolerations: []
+
+affinity: {}
+
+rapp:
+  securityEnabled: true 
+  type: provider
+  realm: x509
+  client: x509provider-cli 
+  roles: 
+  - role : provider-viewer
+    grants: 
+      - GET
+  - role : provider-admin
+    grants: 
+      - GET
+      - PUT
+      - PUT
+      - DELETE 
+
diff --git a/rapps/go.mod b/rapps/go.mod
index d9b2596..5d99959 100644
--- a/rapps/go.mod
+++ b/rapps/go.mod
@@ -4,10 +4,13 @@
 
 require (
 	github.com/Nerzal/gocloak/v10 v10.0.1
+	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 	github.com/pkg/errors v0.9.1
+	golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871
 	gopkg.in/yaml.v2 v2.4.0
 	helm.sh/helm/v3 v3.8.0
 	istio.io/client-go v1.13.0
+	k8s.io/api v0.23.4
 	k8s.io/apimachinery v0.23.4
 	k8s.io/cli-runtime v0.23.4
 	k8s.io/client-go v0.23.4
@@ -108,7 +111,6 @@
 	github.com/xeipuuv/gojsonschema v1.2.0 // indirect
 	github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect
 	go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
-	golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 // indirect
 	golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d // indirect
 	golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
@@ -125,7 +127,6 @@
 	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
 	istio.io/api v0.0.0-20220208030606-6253688c0c91 // indirect
 	istio.io/gogo-genproto v0.0.0-20211208193508-5ab4acc9eb1e // indirect
-	k8s.io/api v0.23.4 // indirect
 	k8s.io/apiextensions-apiserver v0.23.1 // indirect
 	k8s.io/apiserver v0.23.1 // indirect
 	k8s.io/component-base v0.23.1 // indirect
diff --git a/rapps/go.sum b/rapps/go.sum
index 1539f8e..2caa5ae 100644
--- a/rapps/go.sum
+++ b/rapps/go.sum
@@ -339,6 +339,7 @@
 github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
 github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0=
 github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
 github.com/distribution/distribution/v3 v3.0.0-20211118083504-a29a3c99a684 h1:DBZ2sN7CK6dgvHVpQsQj4sRMCbWTmd17l+5SUCjnQSY=
diff --git a/rapps/keycloak.yaml b/rapps/keycloak.yaml
index 44aa3a7..8aa56cd 100644
--- a/rapps/keycloak.yaml
+++ b/rapps/keycloak.yaml
@@ -131,3 +131,66 @@
         persistentVolumeClaim:
           claimName: keycloak-certs-pv-claim
 ---
+apiVersion: networking.istio.io/v1alpha3
+kind: Gateway
+metadata:
+  name: kcgateway
+spec:
+  selector:
+    istio: ingressgateway # use istio default ingress gateway
+  servers:
+  - port:
+      number: 443
+      name: https
+      protocol: HTTPS
+    tls:
+      mode: PASSTHROUGH
+    hosts:
+    - keycloak.est.tech
+  - port:
+      number: 80
+      name: http
+      protocol: HTTP
+    hosts:
+    - "*"
+---
+apiVersion: networking.istio.io/v1alpha3
+kind: VirtualService
+metadata:
+  name: keycloak-tls-vs
+spec:
+  hosts:
+  - keycloak.est.tech
+  gateways:
+  - kcgateway
+  tls:
+  - match:
+    - port: 443
+      sniHosts:
+      - keycloak.est.tech
+    route:
+    - destination:
+        host: keycloak.default.svc.cluster.local
+        port:
+          number: 8443
+---
+apiVersion: networking.istio.io/v1beta1
+kind: VirtualService
+metadata:
+  name: keycloak-vs
+spec:
+  hosts:
+  - "*"
+  gateways:
+  - kcgateway 
+  http:
+  - name: "keycloak-routes"
+    match:
+    - uri:
+        prefix: "/auth"
+    route:
+    - destination:
+        port:
+          number: 8080
+        host: keycloak.default.svc.cluster.local
+---
diff --git a/rapps/rapps-helm-installer.go b/rapps/rapps-helm-installer.go
index dc49289..6baeca0 100644
--- a/rapps/rapps-helm-installer.go
+++ b/rapps/rapps-helm-installer.go
@@ -2,6 +2,7 @@
 
 import (
 	"context"
+	"database/sql"
 	"encoding/json"
 	"flag"
 	"fmt"
@@ -22,7 +23,6 @@
 	"net/http"
 	"os"
 	"path/filepath"
-        "database/sql"	
 )
 
 var settings *cli.EnvSettings
@@ -35,13 +35,14 @@
 var namespace string
 
 type ChartInfo struct {
-	Name       string
-	Namespace  string
-	Revision   int
-	Updated    string
-	Status     string
-	Chart      string
-	AppVersion string
+	Name       string                 `json:",omitempty"`
+	Namespace  string                 `json:",omitempty"`
+	Revision   int                    `json:",omitempty"`
+	Updated    string                 `json:",omitempty"`
+	Status     string                 `json:",omitempty"`
+	Chart      string                 `json:",omitempty"`
+	AppVersion string                 `json:",omitempty"`
+	Values     map[string]interface{} `json:"-"`
 }
 
 type Rapp struct {
@@ -49,7 +50,6 @@
 	SecurityEnabled bool
 	Realm           string
 	Client          string
-	Keycloak        string
 	Roles           []struct {
 		Role   string
 		Grants []string
@@ -63,11 +63,11 @@
 var rapp Rapp
 
 const (
-    host     = "postgres.default"
-    port     = 5432
-    user     = "capif"
-    password = "capif"
-    dbname   = "capif"
+	host     = "postgres.default"
+	port     = 5432
+	user     = "capif"
+	password = "capif"
+	dbname   = "capif"
 )
 
 func runInstall(res http.ResponseWriter, req *http.Request) {
@@ -82,52 +82,56 @@
 	chartMuseumService, chartMuseumPort := findService("chartmuseum", "default")
 	fmt.Printf("Chart Museum service:%s, Port:%d\n", chartMuseumService, chartMuseumPort)
 	url := "http://" + chartMuseumService + ":" + fmt.Sprint(chartMuseumPort)
-	// Add repo
-	fmt.Printf("Adding %s to Helm Repo\n", url)
-	_, err := addToRepo(url)
-	if err != nil {
-		msg = err.Error()
-	} else {
-		install, err = dryRun()
+	if !chartInstalled(chartName) {
+		// Add repo
+		fmt.Printf("Adding %s to Helm Repo\n", url)
+		_, err := addToRepo(url)
 		if err != nil {
 			msg = err.Error()
 		} else {
-			if rapp.SecurityEnabled && rapp.Type == "provider" {
-				// keycloak client setup
-				fmt.Println("Setting up keycloak")
-				_, err = http.Get("http://rapps-keycloak-mgr.default/create?realm=" + rapp.Realm + "&name=" + rapp.Client + "&role=" + rapp.Roles[0].Role)
-				if err != nil {
-					msg = err.Error()
-				} else {
-					fmt.Println("Setting up istio")
-					_, err := http.Get("http://rapps-istio-mgr.default/create?name=" + chartName + "&realm=" + rapp.Realm + "&role=" + rapp.Roles[0].Role + "&method=" + rapp.Roles[0].Grants[0])
+			install, err = dryRun()
+			if err != nil {
+				msg = err.Error()
+			} else {
+				if rapp.SecurityEnabled && rapp.Type == "provider" {
+					// keycloak client setup
+					fmt.Println("Setting up keycloak")
+					_, err = http.Get("http://rapps-keycloak-mgr.default/create?realm=" + rapp.Realm + "&name=" + rapp.Client + "&role=" + rapp.Roles[0].Role)
 					if err != nil {
 						msg = err.Error()
 					} else {
-						// Install chart
-						fmt.Printf("Installing chart %s to %s namespace\n", chartName, namespace)
-						chart, err = installHelmChart(install)
+						fmt.Println("Setting up istio")
+						_, err := http.Get("http://rapps-istio-mgr.default/create?name=" + chartName + "&realm=" + rapp.Realm + "&role=" + rapp.Roles[0].Role + "&method=" + rapp.Roles[0].Grants[0])
 						if err != nil {
-							msg = "Error occurred during installation " + err.Error()
+							msg = err.Error()
 						} else {
-							msg = "Successfully installed release: " + chart
+							// Install chart
+							fmt.Printf("Installing chart %s to %s namespace\n", chartName, namespace)
+							chart, err = installHelmChart(install)
+							if err != nil {
+								msg = "Error occurred during installation " + err.Error()
+							} else {
+								msg = "Successfully installed release: " + chart
+							}
 						}
 					}
-				}
-			} else {
-				// Install chart
-				fmt.Printf("Installing chart %s to %s namespace\n", chartName, namespace)
-				chart, err = installHelmChart(install)
-				if err != nil {
-					msg = "Error occurred during installation " + err.Error()
 				} else {
-					msg = "Successfully installed release: " + chart
+					// Install chart
+					fmt.Printf("Installing chart %s to %s namespace\n", chartName, namespace)
+					chart, err = installHelmChart(install)
+					if err != nil {
+						msg = "Error occurred during installation " + err.Error()
+					} else {
+						msg = "Successfully installed release: " + chart
+					}
 				}
-			}
 
+			}
 		}
+		registrerRapp(chartName, rapp.Type)
+	} else {
+		msg = chartName + " has already been installed"
 	}
-	registrerRapp(chartName, rapp.Type) 
 
 	// create response binary data
 	data := []byte(msg) // slice of bytes
@@ -143,33 +147,36 @@
 
 	var msg string
 	var chart string
-	//var install *action.Install
-	_, err := dryRun()
-	if err != nil {
-		msg = err.Error()
-	} else {
-		chart, err = uninstallHelmChart(chartName)
+	if chartInstalled(chartName) {
+		err := getChartValues(chartName)
 		if err != nil {
-			msg = "Error occurred during uninstall " + err.Error()
+			msg = err.Error()
 		} else {
-			msg = "Successfully uninstalled release: " + chart
-		}
-		if rapp.SecurityEnabled && rapp.Type == "provider" {
-			// Remove istio objects for rapp
-			fmt.Println("Removing istio services")
-			_, err := http.Get("http://rapps-istio-mgr.default/remove?name=" + chartName)
+			chart, err = uninstallHelmChart(chartName)
 			if err != nil {
-				msg = err.Error()
+				msg = "Error occurred during uninstall " + err.Error()
+			} else {
+				msg = "Successfully uninstalled release: " + chart
 			}
-			// remove keycloak client
-			fmt.Println("Removing keycloak client")
-			_, err = http.Get("http://rapps-keycloak-mgr.default/remove?realm=" + rapp.Realm + "&name=" + rapp.Client + "&role=" + rapp.Roles[0].Role)
-			if err != nil {
-				msg = err.Error()
+			if rapp.SecurityEnabled && rapp.Type == "provider" {
+				// Remove istio objects for rapp
+				fmt.Println("Removing istio services")
+				_, err := http.Get("http://rapps-istio-mgr.default/remove?name=" + chartName)
+				if err != nil {
+					msg = err.Error()
+				}
+				// remove keycloak client
+				fmt.Println("Removing keycloak client")
+				_, err = http.Get("http://rapps-keycloak-mgr.default/remove?realm=" + rapp.Realm + "&name=" + rapp.Client + "&role=" + rapp.Roles[0].Role)
+				if err != nil {
+					msg = err.Error()
+				}
 			}
 		}
+		unregistrerRapp(chartName, rapp.Type)
+	} else {
+		msg = chartName + " is not installed"
 	}
-	unregistrerRapp(chartName, rapp.Type)
 
 	// create response binary data
 	data := []byte(msg) // slice of bytes
@@ -388,7 +395,7 @@
 		fmt.Println(err)
 	}
 	for _, release := range releases {
-		fmt.Println("Release: " + release.Name + " Status: " + release.Info.Status.String())
+		//fmt.Println("Release: " + release.Name + " Status: " + release.Info.Status.String())
 		chart.Name = release.Name
 		chart.Namespace = release.Namespace
 		chart.Revision = release.Version
@@ -396,66 +403,100 @@
 		chart.Status = release.Info.Status.String()
 		chart.Chart = release.Chart.Metadata.Name + "-" + release.Chart.Metadata.Version
 		chart.AppVersion = release.Chart.Metadata.AppVersion
+		chart.Values = release.Chart.Values
 		charts = append(charts, chart)
 	}
 	return charts
 }
 
+func chartInstalled(chartName string) bool {
+	charts := list()
+	for _, chart := range charts {
+		if chart.Name == chartName {
+			return true
+		}
+	}
+	return false
+}
+
+func getChartValues(chartName string) error {
+	charts := list()
+	for _, chart := range charts {
+		if chart.Name == chartName {
+			rappMap := chart.Values["rapp"]
+			fmt.Println("rappMap:", rappMap)
+			// Convert map to json string
+			jsonStr, err := json.Marshal(rappMap)
+			if err != nil {
+				fmt.Println("Error:", err)
+				return err
+			}
+
+			if err := json.Unmarshal(jsonStr, &rapp); err != nil {
+				fmt.Println("Error:", err)
+				return err
+			}
+			return nil
+		}
+	}
+	return errors.New("Chart: cannot retrieve values")
+}
+
 func registrerRapp(chartName, chartType string) {
-    psqlconn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host, port, user, password, dbname)
+	psqlconn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host, port, user, password, dbname)
 
-    db, err := sql.Open("postgres", psqlconn)
-    if err != nil{
-       fmt.Println(err)
-    } else {
-       fmt.Println("Connected!")
-    }
+	db, err := sql.Open("postgres", psqlconn)
+	if err != nil {
+		fmt.Println(err)
+	} else {
+		fmt.Println("Connected!")
+	}
 
-    defer db.Close()
+	defer db.Close()
 
-    // create 
-    // hardcoded
-    createStmt := `CREATE TABLE IF NOT EXISTS services (
+	// create
+	// hardcoded
+	createStmt := `CREATE TABLE IF NOT EXISTS services (
 	id serial PRIMARY KEY,
 	name VARCHAR ( 50 ) UNIQUE NOT NULL,
 	type VARCHAR ( 50 ) NOT NULL,
 	created_on TIMESTAMP DEFAULT NOW() 
         );`
-    resp, err := db.Exec(createStmt)
-    if err != nil{
-       fmt.Println(err)
-    } else {
-       fmt.Println(resp)
-    }
+	_, err = db.Exec(createStmt)
+	if err != nil {
+		fmt.Println(err)
+	} else {
+		fmt.Println("Created table for service registry")
+	}
 
-    // dynamic
-    insertDynStmt := `insert into "services"("name", "type") values($1, $2)`
-    _, err = db.Exec(insertDynStmt, chartName, chartType)
-    if err != nil{
-       fmt.Println(err)
-    }  else {
-       fmt.Println(resp)
-    }
+	// dynamic
+	insertDynStmt := `insert into "services"("name", "type") values($1, $2)`
+	_, err = db.Exec(insertDynStmt, chartName, chartType)
+	if err != nil {
+		fmt.Println(err)
+	} else {
+		fmt.Println("Inserted " + chartName + " into service registry")
+	}
 }
 
 func unregistrerRapp(chartName, chartType string) {
-    psqlconn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host, port, user, password, dbname)
+	psqlconn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host, port, user, password, dbname)
 
-    db, err := sql.Open("postgres", psqlconn)
-    if err != nil{
-       fmt.Println(err)
-    } else {
-       fmt.Println("Connected!")
-    }
+	db, err := sql.Open("postgres", psqlconn)
+	if err != nil {
+		fmt.Println(err)
+	} else {
+		fmt.Println("Connected!")
+	}
 
-    defer db.Close()
+	defer db.Close()
 
-    // dynamic
-    deleteDynStmt := `delete from services where name=$1 and type=$2`
-    resp, err := db.Exec(deleteDynStmt, chartName, chartType)
-    if err != nil{
-       fmt.Println(err)
-    }  else {
-       fmt.Println(resp)
-    }
+	// dynamic
+	deleteDynStmt := `delete from services where name=$1 and type=$2`
+	_, err = db.Exec(deleteDynStmt, chartName, chartType)
+	if err != nil {
+		fmt.Println(err)
+	} else {
+		fmt.Println("Deleted " + chartName + " from service registry")
+	}
 }
diff --git a/rapps/rapps-istio-mgr.go b/rapps/rapps-istio-mgr.go
index 04189ce..93ed8bb 100644
--- a/rapps/rapps-istio-mgr.go
+++ b/rapps/rapps-istio-mgr.go
@@ -78,6 +78,14 @@
     jwksUri: "http://192.168.49.2:31560/auth/realms/REALM-NAME/protocol/openid-connect/certs"
   - issuer: "http://keycloak.default:8080/auth/realms/REALM-NAME"
     jwksUri: "http://keycloak.default:8080/auth/realms/REALM-NAME/protocol/openid-connect/certs"
+  - issuer: "https://192.168.49.2:31561/auth/realms/REALM-NAME"
+    jwksUri: "https://192.168.49.2:31561/auth/realms/REALM-NAME/protocol/openid-connect/certs"
+  - issuer: "https://keycloak.default:8443/auth/realms/REALM-NAME"
+    jwksUri: "https://keycloak.default:8443/auth/realms/REALM-NAME/protocol/openid-connect/certs"
+  - issuer: "https://keycloak.est.tech:443/auth/realms/REALM-NAME"
+    jwksUri: "https://keycloak.default:8443/auth/realms/REALM-NAME/protocol/openid-connect/certs"
+  - issuer: "http://istio-ingressgateway.istio-system:80/auth/realms/REALM-NAME"
+    jwksUri: "http://keycloak.default:8080/auth/realms/REALM-NAME/protocol/openid-connect/certs"
 `
 
 var authorizationPolicyManifest = `
@@ -94,11 +102,11 @@
   rules:
   - from:
     - source:
-        requestPrincipals: ["http://192.168.49.2:31560/auth/realms/REALM-NAME/", "http://keycloak.default:8080/auth/realms/REALM-NAME/"]
+        requestPrincipals: ["http://192.168.49.2:31560/auth/realms/REALM-NAME/", "http://keycloak.default:8080/auth/realms/REALM-NAME/", "https://192.168.49.2:31561/auth/realms/REALM-NAME/", "https://keycloak.default:8443/auth/realms/REALM-NAME/", "https://keycloak.est.tech:443/auth/realms/REALM-NAME/", "http://istio-ingressgateway.istio-system:80/auth/realms/REALM-NAME/"]
   - to:
     - operation:
         methods: ["METHOD-NAME"]
-        paths: ["/RAPP-NAME*"]
+        paths: ["/RAPP-NAME"]
     when:
     - key: request.auth.claims[clientRole]
       values: ["ROLE-NAME"]
@@ -133,11 +141,11 @@
 
 func createGateway(clientset *versioned.Clientset, appName string) (string, error) {
 	gtClient := clientset.NetworkingV1beta1().Gateways(NAMESPACE)
-	gatewayManifest = strings.Replace(gatewayManifest, "RAPP-NAME", appName, -1)
-	gatewayManifest = strings.Replace(gatewayManifest, "RAPP-NS", NAMESPACE, -1)
+	manifest := strings.Replace(gatewayManifest, "RAPP-NAME", appName, -1)
+	manifest = strings.Replace(manifest, "RAPP-NS", NAMESPACE, -1)
 
 	gt := &netv1beta1.Gateway{}
-	dec := k8Yaml.NewYAMLOrJSONDecoder(bytes.NewReader([]byte(gatewayManifest)), 1000)
+	dec := k8Yaml.NewYAMLOrJSONDecoder(bytes.NewReader([]byte(manifest)), 1000)
 
 	if err := dec.Decode(&gt); err != nil {
 		return "", err
@@ -155,11 +163,11 @@
 
 func createVirtualService(clientset *versioned.Clientset, appName string) (string, error) {
 	vsClient := clientset.NetworkingV1beta1().VirtualServices(NAMESPACE)
-	virtualServiceManifest = strings.Replace(virtualServiceManifest, "RAPP-NAME", appName, -1)
-	virtualServiceManifest = strings.Replace(virtualServiceManifest, "RAPP-NS", NAMESPACE, -1)
+	manifest := strings.Replace(virtualServiceManifest, "RAPP-NAME", appName, -1)
+	manifest = strings.Replace(manifest, "RAPP-NS", NAMESPACE, -1)
 
 	vs := &netv1beta1.VirtualService{}
-	dec := k8Yaml.NewYAMLOrJSONDecoder(bytes.NewReader([]byte(virtualServiceManifest)), 1000)
+	dec := k8Yaml.NewYAMLOrJSONDecoder(bytes.NewReader([]byte(manifest)), 1000)
 
 	if err := dec.Decode(&vs); err != nil {
 		return "", err
@@ -177,12 +185,12 @@
 
 func createRequestAuthentication(clientset *versioned.Clientset, appName, realmName string) (string, error) {
 	raClient := clientset.SecurityV1beta1().RequestAuthentications(NAMESPACE)
-	requestAuthenticationManifest = strings.Replace(requestAuthenticationManifest, "RAPP-NAME", appName, -1)
-	requestAuthenticationManifest = strings.Replace(requestAuthenticationManifest, "REALM-NAME", realmName, -1)
-	requestAuthenticationManifest = strings.Replace(requestAuthenticationManifest, "RAPP-NS", NAMESPACE, -1)
+	manifest := strings.Replace(requestAuthenticationManifest, "RAPP-NAME", appName, -1)
+	manifest = strings.Replace(manifest, "REALM-NAME", realmName, -1)
+	manifest = strings.Replace(manifest, "RAPP-NS", NAMESPACE, -1)
 
 	ra := &secv1beta1.RequestAuthentication{}
-	dec := k8Yaml.NewYAMLOrJSONDecoder(bytes.NewReader([]byte(requestAuthenticationManifest)), 1000)
+	dec := k8Yaml.NewYAMLOrJSONDecoder(bytes.NewReader([]byte(manifest)), 1000)
 
 	if err := dec.Decode(&ra); err != nil {
 		return "", err
@@ -200,14 +208,14 @@
 
 func createAuthorizationPolicy(clientset *versioned.Clientset, appName, realmName, roleName, methodName string) (string, error) {
 	apClient := clientset.SecurityV1beta1().AuthorizationPolicies(NAMESPACE)
-	authorizationPolicyManifest = strings.Replace(authorizationPolicyManifest, "RAPP-NAME", appName, -1)
-	authorizationPolicyManifest = strings.Replace(authorizationPolicyManifest, "REALM-NAME", realmName, -1)
-	authorizationPolicyManifest = strings.Replace(authorizationPolicyManifest, "ROLE-NAME", roleName, -1)
-	authorizationPolicyManifest = strings.Replace(authorizationPolicyManifest, "METHOD-NAME", methodName, -1)
-	authorizationPolicyManifest = strings.Replace(authorizationPolicyManifest, "RAPP-NS", NAMESPACE, -1)
+	manifest := strings.Replace(authorizationPolicyManifest, "RAPP-NAME", appName, -1)
+	manifest = strings.Replace(manifest, "REALM-NAME", realmName, -1)
+	manifest = strings.Replace(manifest, "ROLE-NAME", roleName, -1)
+	manifest = strings.Replace(manifest, "METHOD-NAME", methodName, -1)
+	manifest = strings.Replace(manifest, "RAPP-NS", NAMESPACE, -1)
 
 	ap := &secv1beta1.AuthorizationPolicy{}
-	dec := k8Yaml.NewYAMLOrJSONDecoder(bytes.NewReader([]byte(authorizationPolicyManifest)), 1000)
+	dec := k8Yaml.NewYAMLOrJSONDecoder(bytes.NewReader([]byte(manifest)), 1000)
 
 	if err := dec.Decode(&ap); err != nil {
 		return "", err
diff --git a/rapps/rapps-keycloak-mgr.go b/rapps/rapps-keycloak-mgr.go
index 0d0a29c..82097ff 100644
--- a/rapps/rapps-keycloak-mgr.go
+++ b/rapps/rapps-keycloak-mgr.go
@@ -9,10 +9,12 @@
 	kubernetes "k8s.io/client-go/kubernetes"
 	"k8s.io/client-go/rest"
 	"net/http"
+	"strings"
+	"rapps/utils/pemtojwks"
 )
 
 const (
-  namespace = "istio-nonrtric"
+	namespace = "istio-nonrtric"
 )
 
 func createClient(res http.ResponseWriter, req *http.Request) {
@@ -25,7 +27,9 @@
 	if err != nil {
 		msg = err.Error()
 	}
-	createSecret(msg, clientName, realmName, role, namespace)
+	if realmName != "x509" && realmName != "jwt" {
+		createSecret(msg, clientName, realmName, role, namespace)
+	}
 	// create response binary data
 	data := []byte(msg) // slice of bytes
 	// write `data` to response
@@ -40,7 +44,9 @@
 
 	var msg string = "Removed keycloak " + clientName + " from " + realmName + " realm"
 	remove(realmName, clientName)
-	removeSecret(namespace, role)
+        if realmName != "x509" && realmName != "jwt" {
+	        removeSecret(namespace, role)
+        }
 	// create response binary data
 	data := []byte(msg) // slice of bytes
 	// write `data` to response
@@ -82,7 +88,21 @@
 		fmt.Println("Realm already exists", realmName)
 	}
 
-	newClient := gocloak.Client{
+	flowAlias := "x509 direct grant"
+	flowId := ""
+	flows, err := client.GetAuthenticationFlows(ctx, token.AccessToken, realmName)
+	if err != nil {
+		fmt.Println("Oh no!, failed to get flows :(")
+	} else {
+		for _, flow := range flows {
+			if flow.Alias != nil && *flow.Alias == flowAlias {
+				flowId = *flow.ID
+			}
+		}
+		fmt.Println("Retrieved AuthenticationFlow id", flowId)
+	}
+
+	newClient1 := gocloak.Client{
 		ClientID:                  gocloak.StringP(clientName),
 		Enabled:                   gocloak.BoolP(true),
 		DirectAccessGrantsEnabled: gocloak.BoolP(true),
@@ -91,11 +111,54 @@
 		ServiceAccountsEnabled:    gocloak.BoolP(true),
 		ClientAuthenticatorType:   gocloak.StringP("client-secret"),
 		DefaultClientScopes:       &[]string{"email"},
-		Attributes:                &map[string]string{"use.refresh.tokens": "true",
-	                                                      "client_credentials.use_refresh_token": "true"},
+		Attributes: &map[string]string{"use.refresh.tokens": "true",
+			"client_credentials.use_refresh_token": "true"},
 	}
+
+	newClient2 := gocloak.Client{
+		ClientID:                  gocloak.StringP(clientName),
+		Enabled:                   gocloak.BoolP(true),
+		DirectAccessGrantsEnabled: gocloak.BoolP(true),
+		BearerOnly:                gocloak.BoolP(false),
+		PublicClient:              gocloak.BoolP(false),
+		ServiceAccountsEnabled:    gocloak.BoolP(true),
+		ClientAuthenticatorType:   gocloak.StringP("client-x509"),
+		DefaultClientScopes:       &[]string{"openid", "profile", "email"},
+		Attributes: &map[string]string{"use.refresh.tokens": "true",
+			"client_credentials.use_refresh_token": "true",
+			"x509.subjectdn":                       ".*client@mail.com.*",
+			"x509.allow.regex.pattern.comparison":  "true"},
+		AuthenticationFlowBindingOverrides: &map[string]string{"direct_grant": flowId},
+	}
+
+        jwksString := pemtojwks.CreateJWKS("/certs/client_pub.key", "public", "/certs/client.crt") 
+	newClient3 := gocloak.Client{
+                ClientID:                  gocloak.StringP(clientName),
+                Enabled:                   gocloak.BoolP(true),
+                DirectAccessGrantsEnabled: gocloak.BoolP(true),
+                BearerOnly:                gocloak.BoolP(false),
+                PublicClient:              gocloak.BoolP(false),
+                ServiceAccountsEnabled:    gocloak.BoolP(true),
+                ClientAuthenticatorType:   gocloak.StringP("client-jwt"),
+                DefaultClientScopes:       &[]string{"email"},
+                Attributes: &map[string]string{"token.endpoint.auth.signing.alg": "RS256",
+		       "use.jwks.string": "true",
+                       "jwks.string": jwksString, 
+		},
+        }
+
+	var newClient gocloak.Client
+	if strings.HasPrefix(clientName, "x509") {
+		newClient = newClient2
+	} else if strings.HasPrefix(clientName, "jwt") {
+		newClient = newClient3
+	} else {
+                newClient = newClient1
+        }
+
 	clientId, err := client.CreateClient(ctx, token.AccessToken, realmName, newClient)
 	if err != nil {
+		fmt.Println("Failed to create client", err)
 		return "", err
 	} else {
 		fmt.Println("Created realm client", clientId)
@@ -119,6 +182,23 @@
 		fmt.Println("Service Account user", *user.Username)
 	}
 
+	if strings.HasPrefix(clientName, "x509") {
+		newUser := gocloak.User{
+			ID:       gocloak.StringP(realmName + "user"),
+			Username: gocloak.StringP(realmName + "user"),
+			Email:    gocloak.StringP("client@mail.com"),
+			Enabled:  gocloak.BoolP(true),
+		}
+
+		realmUser, err := client.CreateUser(ctx, token.AccessToken, realmName, newUser)
+		if err != nil {
+			fmt.Println(err)
+			panic("Oh no!, failed to create user :(")
+		} else {
+			fmt.Println("Created new user", realmUser)
+		}
+	}
+
 	clientRole, err := client.GetClientRole(ctx, token.AccessToken, realmName, clientId, clientRoleName)
 	if err != nil {
 		fmt.Println(err)
@@ -137,8 +217,8 @@
 	}
 
 	clientroleMapper := gocloak.ProtocolMapperRepresentation{
-		ID:             gocloak.StringP("clientroleapper"),
-		Name:           gocloak.StringP("clientroleapper"),
+		ID:             gocloak.StringP("Client Role " + clientName + " Mapper"),
+		Name:           gocloak.StringP("Client Role " + clientName + " Mapper"),
 		Protocol:       gocloak.StringP("openid-connect"),
 		ProtocolMapper: gocloak.StringP("oidc-usermodel-client-role-mapper"),
 		Config: &map[string]string{
@@ -160,6 +240,26 @@
 		fmt.Println("Client rolemapper added to client")
 	}
 
+	if strings.HasPrefix(clientName, "x509") {
+		clientRole := *newClient.ClientID + "." + clientRoleName
+
+		clientroleMapper := gocloak.ProtocolMapperRepresentation{
+			ID:             gocloak.StringP("Hardcoded " + clientName + " Mapper"),
+			Name:           gocloak.StringP("Hardcoded " + clientName + " Mapper"),
+			Protocol:       gocloak.StringP("openid-connect"),
+			ProtocolMapper: gocloak.StringP("oidc-hardcoded-role-mapper"),
+			Config: &map[string]string{
+				"role": clientRole,
+			},
+		}
+		_, err = client.CreateClientProtocolMapper(ctx, token.AccessToken, realmName, clientId, clientroleMapper)
+		if err != nil {
+			return "", err
+		} else {
+			fmt.Println("Created hardcoded-role-mapper for ", clientRole)
+		}
+	}
+
 	_, err = client.RegenerateClientSecret(ctx, token.AccessToken, realmName, clientId)
 	if err != nil {
 		return "", err
@@ -190,7 +290,7 @@
 }
 
 func createSecret(clientSecret, clientName, realmName, role, namespace string) {
-	secretName := role +"-secret"
+	secretName := role + "-secret"
 	clientset := connectToK8s()
 	secrets := clientset.CoreV1().Secrets(namespace)
 	secret := &corev1.Secret{
@@ -198,8 +298,8 @@
 			Name:      secretName,
 			Namespace: namespace,
 			Labels: map[string]string{
-                                        "app" : secretName,
-                                },
+				"app": secretName,
+			},
 		},
 		Type:       "Opaque",
 		StringData: map[string]string{"client_secret": clientSecret, "client_id": clientName, "realm": realmName},
@@ -237,11 +337,29 @@
 			fmt.Println("Deleted client ", clientName)
 		}
 	}
+
+	userName := realmName + "user"
+	users, err := adminClient.GetUsers(ctx, token.AccessToken, realmName,
+		gocloak.GetUsersParams{
+			Username: gocloak.StringP(userName),
+		})
+	if err != nil {
+		panic("List users failed:" + err.Error())
+	}
+	for _, user := range users {
+		err = adminClient.DeleteUser(ctx, token.AccessToken, realmName, *user.ID)
+		if err != nil {
+			fmt.Println(err)
+		} else {
+			fmt.Println("Deleted user ", userName)
+		}
+	}
+
 }
 
 func removeSecret(namespace, role string) {
 	clientset := connectToK8s()
-	secretName := role +"-secret"
+	secretName := role + "-secret"
 	secrets := clientset.CoreV1().Secrets(namespace)
 	err := secrets.Delete(context.TODO(), secretName, metav1.DeleteOptions{})
 	if err != nil {
diff --git a/rapps/rapps-keycloak-mgr.yaml b/rapps/rapps-keycloak-mgr.yaml
index b7113b7..fd00a52 100644
--- a/rapps/rapps-keycloak-mgr.yaml
+++ b/rapps/rapps-keycloak-mgr.yaml
@@ -1,3 +1,34 @@
+apiVersion: v1
+kind: PersistentVolume
+metadata:
+  name: keycloak-mgr-pv-volume
+  namespace: default 
+  labels:
+    app: rapps-keycloak-mgr 
+spec:
+  storageClassName: manual
+  capacity:
+    storage: 10Mi
+  accessModes:
+    - ReadOnlyMany 
+  hostPath:
+    path: "/var/rapps/certs"
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  name: keycloak-mgr-pv-claim
+  namespace: default 
+  labels:
+    app: rapps-keycloak-mgr 
+spec:
+  storageClassName: manual
+  accessModes:
+    - ReadOnlyMany 
+  resources:
+    requests:
+      storage: 10Mi
+---
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -28,6 +59,13 @@
           requests:
             memory: 128Mi
             cpu: "80m"
+        volumeMounts:
+        - name: keycloak-mgr-cert-storage
+          mountPath: /certs
+      volumes:
+      - name: keycloak-mgr-cert-storage 
+        persistentVolumeClaim:
+          claimName: keycloak-mgr-pv-claim
       serviceAccountName: helm-app
   replicas: 1 
 ---
diff --git a/rapps/rapps-rapp-auth-provider.go b/rapps/rapps-rapp-auth-provider.go
new file mode 100644
index 0000000..e84c11c
--- /dev/null
+++ b/rapps/rapps-rapp-auth-provider.go
@@ -0,0 +1,67 @@
+package main
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+)
+
+type Jwttoken struct {
+	Access_token       string
+	Expires_in         int
+	Refresh_expires_in int
+	Refresh_token      string
+	Token_type         string
+	Not_before_policy  int
+	Session_state      string
+	Scope              string
+}
+
+var jwt Jwttoken
+
+func getToken(auth_code string) string {
+	clientSecret := "Ctz6aBahmjQvAt7Lwgg8qDNsniuPkNCC"
+	clientId := "jwtsecret"
+	realmName := "jwtrealm"
+	keycloakHost := "keycloak"
+	keycloakPort := "8080"
+	keycloakUrl := "http://" + keycloakHost + ":" + keycloakPort + "/auth/realms/" + realmName + "/protocol/openid-connect/token"
+	resp, err := http.PostForm(keycloakUrl,
+		     url.Values{"code": {auth_code}, "grant_type": {"authorization_code"}, 
+		                "client_id": {clientId}, "client_secret": {clientSecret}})
+	if err != nil {
+		fmt.Println(err)
+		panic("Something wrong with the credentials or url ")
+	}
+	defer resp.Body.Close()
+	body, err := ioutil.ReadAll(resp.Body)
+	fmt.Println(string(body))
+	json.Unmarshal([]byte(body), &jwt)
+	return jwt.Access_token
+}
+
+func noprefix(res http.ResponseWriter, req *http.Request) {
+	// create response binary data
+	data := []byte("Authorization code default") // slice of bytes
+	// write `data` to response
+	res.Write(data)
+}
+
+func callback(res http.ResponseWriter, req *http.Request) {
+	query := req.URL.Query()
+	code := query.Get("code")
+	token := getToken(code)
+	res.WriteHeader(http.StatusOK)
+	res.Write([]byte(token))
+}
+
+func main() {
+	// create a new handler
+	callbackHandler := http.HandlerFunc(callback)
+	http.Handle("/callback", callbackHandler)
+	noPrefixHandler := http.HandlerFunc(noprefix)
+	http.Handle("/", noPrefixHandler)
+	http.ListenAndServe(":9000", nil)
+}
diff --git a/rapps/rapps-rapp-invoker.go b/rapps/rapps-rapp-invoker.go
index d00c589..1a73006 100644
--- a/rapps/rapps-rapp-invoker.go
+++ b/rapps/rapps-rapp-invoker.go
@@ -79,7 +79,7 @@
 	var jsonValue []byte = []byte{}
 	var restUrl string = ""
 
-	if service == "rapp-provider" && securityEnabled == "true" {
+	if securityEnabled == "true" {
 		secretName := role + "-secret"
 		token = getToken(secretName)
 	} else {
@@ -143,8 +143,8 @@
 	time.Sleep(1 * time.Second)
 	flag.StringVar(&gatewayHost, "gatewayHost", "istio-ingressgateway.istio-system", "Gateway Host")
 	flag.StringVar(&gatewayPort, "gatewayPort", "80", "Gateway Port")
-	flag.StringVar(&keycloakHost, "keycloakHost", "keycloak.default", "Keycloak Host")
-	flag.StringVar(&keycloakPort, "keycloakPort", "8080", "Keycloak Port")
+	flag.StringVar(&keycloakHost, "keycloakHost", "istio-ingressgateway.istio-system", "Keycloak Host")
+	flag.StringVar(&keycloakPort, "keycloakPort", "80", "Keycloak Port")
 	flag.StringVar(&useGateway, "useGateway", "Y", "Connect to services through API gateway")
 	flag.StringVar(&securityEnabled, "securityEnabled", "true", "Security is required to use this application")
 	flag.StringVar(&role, "role", "provider-viewer", "Role granted to application")
diff --git a/rapps/rapps-rapp-jwt-invoker.go b/rapps/rapps-rapp-jwt-invoker.go
new file mode 100644
index 0000000..de806ca
--- /dev/null
+++ b/rapps/rapps-rapp-jwt-invoker.go
@@ -0,0 +1,174 @@
+package main
+
+import (
+	"bytes"
+	"encoding/json"
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+	"strings"
+	"time"
+	"rapps/utils/generatejwt"
+)
+
+type Jwttoken struct {
+	Access_token       string
+	Expires_in         int
+	Refresh_expires_in int
+	Refresh_token      string
+	Token_type         string
+	Not_before_policy  int
+	Session_state      string
+	Scope              string
+}
+
+var gatewayHost string
+var gatewayPort string
+var keycloakHost string
+var keycloakPort string
+var keycloakAlias string
+var securityEnabled string
+var useGateway string
+var role string
+var rapp string
+var methods string
+var realmName string
+var clientId string
+var healthy bool = true
+var ttime time.Time
+var jwt Jwttoken
+
+const (
+	namespace = "istio-nonrtric"
+	scope     = "email"
+	client_assertion_type = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
+)
+
+func getToken() string {
+	if ttime.Before(time.Now()) {
+		client_assertion := getClientAssertion()
+		keycloakUrl := "http://" + keycloakHost + ":" + keycloakPort + "/auth/realms/" + realmName + "/protocol/openid-connect/token"
+		resp, err := http.PostForm(keycloakUrl, url.Values{"client_assertion_type": {client_assertion_type}, 
+		             "client_assertion": {client_assertion}, "grant_type": {"client_credentials"}, "client_id": {clientId}, 
+			     "scope": {scope}})
+		if err != nil {
+			fmt.Println(err)
+			panic("Something wrong with the credentials or url ")
+		}
+		defer resp.Body.Close()
+		body, err := ioutil.ReadAll(resp.Body)
+		json.Unmarshal([]byte(body), &jwt)
+		ttime = time.Now()
+		ttime = ttime.Add(time.Second * time.Duration(jwt.Expires_in))
+	}
+	return jwt.Access_token
+}
+
+func getClientAssertion() string {
+	realm := "http://" + keycloakHost + ":" + keycloakPort + "/auth/realms/" + realmName
+        clientAssertion := generatejwt.CreateJWT("/certs/client.key", "/certs/client_pub.key", "", clientId, realm)	
+        return clientAssertion 
+}
+
+func MakeRequest(client *http.Client, prefix string, method string, ch chan string) {
+	var service = strings.Split(prefix, "/")[1]
+	var gatewayUrl = "http://" + gatewayHost + ":" + gatewayPort
+	var token = ""
+	var jsonValue []byte = []byte{}
+	var restUrl string = ""
+
+	if securityEnabled == "true" {
+		token = getToken()
+	} else {
+		useGateway = "N"
+	}
+
+	if strings.ToUpper(useGateway) != "Y" {
+		gatewayUrl = "http://" + service + "." + namespace + ":80"
+		prefix = ""
+	}
+
+	restUrl = gatewayUrl + prefix
+
+	req, err := http.NewRequest(method, restUrl, bytes.NewBuffer(jsonValue))
+	if err != nil {
+		fmt.Printf("Got error %s", err.Error())
+	}
+	req.Header.Set("Content-type", "application/json")
+	req.Header.Set("Authorization", "Bearer "+token)
+
+	resp, err := client.Do(req)
+	if err != nil {
+		fmt.Printf("Got error %s", err.Error())
+	}
+	defer resp.Body.Close()
+	body, _ := ioutil.ReadAll(resp.Body)
+
+	respString := string(body[:])
+	if respString == "RBAC: access denied" {
+		respString += " for " + service + " " + strings.ToLower(method) + " request"
+	}
+	fmt.Printf("Received response for %s %s request - %s\n", service, strings.ToLower(method), respString)
+	ch <- prefix + "," + method
+}
+
+func health(res http.ResponseWriter, req *http.Request) {
+	if healthy {
+		res.WriteHeader(http.StatusOK)
+		res.Write([]byte("healthy"))
+	} else {
+		res.WriteHeader(http.StatusInternalServerError)
+		res.Write([]byte("unhealthy"))
+	}
+}
+
+func main() {
+	ttime = time.Now()
+	time.Sleep(1 * time.Second)
+	flag.StringVar(&gatewayHost, "gatewayHost", "istio-ingressgateway.istio-system", "Gateway Host")
+	flag.StringVar(&gatewayPort, "gatewayPort", "80", "Gateway Port")
+	flag.StringVar(&keycloakHost, "keycloakHost", "istio-ingressgateway.istio-system", "Keycloak Host")
+	flag.StringVar(&keycloakPort, "keycloakPort", "80", "Keycloak Port")
+	flag.StringVar(&useGateway, "useGateway", "Y", "Connect to services through API gateway")
+	flag.StringVar(&securityEnabled, "securityEnabled", "true", "Security is required to use this application")
+	flag.StringVar(&realmName, "realm", "jwt", "Keycloak realm")
+	flag.StringVar(&clientId, "client", "jwtprovider-cli", "Keycloak client")
+	flag.StringVar(&role, "role", "provider-viewer", "Role granted to application")
+	flag.StringVar(&rapp, "rapp", "rapp-jwt-provider", "Name of rapp to invoke")
+	flag.StringVar(&methods, "methods", "GET", "Methods to access application")
+	flag.Parse()
+
+	healthHandler := http.HandlerFunc(health)
+	http.Handle("/health", healthHandler)
+	go func() {
+		http.ListenAndServe(":9000", nil)
+	}()
+
+	client := &http.Client{
+		Timeout: time.Second * 10,
+	}
+
+	ch := make(chan string)
+	var prefixArray []string = []string{"/" + rapp}
+	var methodArray []string = []string{methods}
+	for _, prefix := range prefixArray {
+		for _, method := range methodArray {
+			go MakeRequest(client, prefix, method, ch)
+		}
+	}
+
+	ioutil.WriteFile("init.txt", []byte("Initialization done."), 0644)
+
+	for r := range ch {
+		go func(resp string) {
+			time.Sleep(10 * time.Second)
+			elements := strings.Split(resp, ",")
+			prefix := elements[0]
+			method := elements[1]
+			MakeRequest(client, prefix, method, ch)
+		}(r)
+	}
+
+}
diff --git a/rapps/rapps-rapp-jwt-provider.go b/rapps/rapps-rapp-jwt-provider.go
new file mode 100644
index 0000000..b0abbb4
--- /dev/null
+++ b/rapps/rapps-rapp-jwt-provider.go
@@ -0,0 +1,23 @@
+package main
+
+import (
+	"net/http"
+)
+
+// create a handler struct
+type HttpHandler struct{}
+
+// implement `ServeHTTP` method on `HttpHandler` struct
+func (h HttpHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
+	// create response binary data
+	data := []byte("Hello World JWT!") // slice of bytes
+	// write `data` to response
+	res.Write(data)
+}
+
+func main() {
+	// create a new handler
+	handler := HttpHandler{}
+	// listen and serve
+	http.ListenAndServe(":9000", handler)
+}
diff --git a/rapps/rapps-rapp-x509-invoker.go b/rapps/rapps-rapp-x509-invoker.go
new file mode 100644
index 0000000..bdebc7a
--- /dev/null
+++ b/rapps/rapps-rapp-x509-invoker.go
@@ -0,0 +1,201 @@
+package main
+
+import (
+	"bytes"
+	"context"
+	"crypto/tls"
+	"crypto/x509"
+	"encoding/json"
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"net"
+	"net/http"
+	"net/url"
+	"strings"
+	"time"
+)
+
+type Jwttoken struct {
+	Access_token       string
+	Expires_in         int
+	Refresh_expires_in int
+	Refresh_token      string
+	Token_type         string
+	Not_before_policy  int
+	Session_state      string
+	Scope              string
+}
+
+var gatewayHost string
+var gatewayPort string
+var keycloakHost string
+var keycloakPort string
+var keycloakAlias string
+var securityEnabled string
+var useGateway string
+var role string
+var rapp string
+var methods string
+var realmName string
+var clientId string
+var healthy bool = true
+var ttime time.Time
+var jwt Jwttoken
+
+const (
+	namespace = "istio-nonrtric"
+	scope     = "openid profile"
+)
+
+func getToken() string {
+	if ttime.Before(time.Now()) {
+		client := getClient()
+		keycloakUrl := "https://" + keycloakAlias + ":" + keycloakPort + "/auth/realms/" + realmName + "/protocol/openid-connect/token"
+		resp, err := client.PostForm(keycloakUrl, url.Values{"username": {""}, "password": {""}, "grant_type": {"password"}, "client_id": {clientId}, "scope": {scope}})
+		if err != nil {
+			fmt.Println(err)
+			panic("Something wrong with the credentials or url ")
+		}
+		defer resp.Body.Close()
+		body, err := ioutil.ReadAll(resp.Body)
+		json.Unmarshal([]byte(body), &jwt)
+		ttime = time.Now()
+		ttime = ttime.Add(time.Second * time.Duration(jwt.Expires_in))
+	}
+	return jwt.Access_token
+}
+
+func getClient() *http.Client {
+	caCert, _ := ioutil.ReadFile("/certs/rootCA.crt")
+	caCertPool := x509.NewCertPool()
+	caCertPool.AppendCertsFromPEM(caCert)
+
+	cert, _ := tls.LoadX509KeyPair("/certs/client.crt", "/certs/client.key")
+
+	dialer := &net.Dialer{
+		Timeout:   30 * time.Second,
+		KeepAlive: 30 * time.Second,
+		DualStack: true,
+	}
+
+	client := &http.Client{
+		Transport: &http.Transport{
+			DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
+				fmt.Println("address original =", addr)
+				if addr == keycloakAlias+":"+keycloakPort {
+					addr = keycloakHost + ":" + keycloakPort
+					fmt.Println("address modified =", addr)
+				}
+				return dialer.DialContext(ctx, network, addr)
+			},
+			TLSClientConfig: &tls.Config{
+				RootCAs:      caCertPool,
+				Certificates: []tls.Certificate{cert},
+			},
+		},
+	}
+	return client
+}
+
+func MakeRequest(client *http.Client, prefix string, method string, ch chan string) {
+	var service = strings.Split(prefix, "/")[1]
+	var gatewayUrl = "http://" + gatewayHost + ":" + gatewayPort
+	var token = ""
+	var jsonValue []byte = []byte{}
+	var restUrl string = ""
+
+	if securityEnabled == "true" {
+		token = getToken()
+	} else {
+		useGateway = "N"
+	}
+
+	if strings.ToUpper(useGateway) != "Y" {
+		gatewayUrl = "http://" + service + "." + namespace + ":80"
+		prefix = ""
+	}
+
+	restUrl = gatewayUrl + prefix
+
+	req, err := http.NewRequest(method, restUrl, bytes.NewBuffer(jsonValue))
+	if err != nil {
+		fmt.Printf("Got error %s", err.Error())
+	}
+	req.Header.Set("Content-type", "application/json")
+	req.Header.Set("Authorization", "Bearer "+token)
+
+	resp, err := client.Do(req)
+	if err != nil {
+		fmt.Printf("Got error %s", err.Error())
+	}
+	defer resp.Body.Close()
+	body, _ := ioutil.ReadAll(resp.Body)
+
+	respString := string(body[:])
+	if respString == "RBAC: access denied" {
+		respString += " for " + service + " " + strings.ToLower(method) + " request"
+	}
+	fmt.Printf("Received response for %s %s request - %s\n", service, strings.ToLower(method), respString)
+	ch <- prefix + "," + method
+}
+
+func health(res http.ResponseWriter, req *http.Request) {
+	if healthy {
+		res.WriteHeader(http.StatusOK)
+		res.Write([]byte("healthy"))
+	} else {
+		res.WriteHeader(http.StatusInternalServerError)
+		res.Write([]byte("unhealthy"))
+	}
+}
+
+func main() {
+	ttime = time.Now()
+	time.Sleep(1 * time.Second)
+	flag.StringVar(&gatewayHost, "gatewayHost", "istio-ingressgateway.istio-system", "Gateway Host")
+	flag.StringVar(&gatewayPort, "gatewayPort", "80", "Gateway Port")
+	flag.StringVar(&keycloakHost, "keycloakHost", "istio-ingressgateway.istio-system", "Keycloak Host")
+	flag.StringVar(&keycloakPort, "keycloakPort", "443", "Keycloak Port")
+	flag.StringVar(&keycloakAlias, "keycloakAlias", "keycloak.est.tech", "Keycloak URL Alias")
+	flag.StringVar(&useGateway, "useGateway", "Y", "Connect to services through API gateway")
+	flag.StringVar(&securityEnabled, "securityEnabled", "true", "Security is required to use this application")
+	flag.StringVar(&realmName, "realm", "x509", "Keycloak realm")
+	flag.StringVar(&clientId, "client", "x509provider-cli", "Keycloak client")
+	flag.StringVar(&role, "role", "provider-viewer", "Role granted to application")
+	flag.StringVar(&rapp, "rapp", "rapp-x509-provider", "Name of rapp to invoke")
+	flag.StringVar(&methods, "methods", "GET", "Methods to access application")
+	flag.Parse()
+
+	healthHandler := http.HandlerFunc(health)
+	http.Handle("/health", healthHandler)
+	go func() {
+		http.ListenAndServe(":9000", nil)
+	}()
+
+	client := &http.Client{
+		Timeout: time.Second * 10,
+	}
+
+	ch := make(chan string)
+	var prefixArray []string = []string{"/" + rapp}
+	var methodArray []string = []string{methods}
+	for _, prefix := range prefixArray {
+		for _, method := range methodArray {
+			go MakeRequest(client, prefix, method, ch)
+		}
+	}
+
+	ioutil.WriteFile("init.txt", []byte("Initialization done."), 0644)
+
+	for r := range ch {
+		go func(resp string) {
+			time.Sleep(10 * time.Second)
+			elements := strings.Split(resp, ",")
+			prefix := elements[0]
+			method := elements[1]
+			MakeRequest(client, prefix, method, ch)
+		}(r)
+	}
+
+}
diff --git a/rapps/rapps-rapp-x509-provider.go b/rapps/rapps-rapp-x509-provider.go
new file mode 100644
index 0000000..f4bf4a8
--- /dev/null
+++ b/rapps/rapps-rapp-x509-provider.go
@@ -0,0 +1,23 @@
+package main
+
+import (
+	"net/http"
+)
+
+// create a handler struct
+type HttpHandler struct{}
+
+// implement `ServeHTTP` method on `HttpHandler` struct
+func (h HttpHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
+	// create response binary data
+	data := []byte("Hello World X509!") // slice of bytes
+	// write `data` to response
+	res.Write(data)
+}
+
+func main() {
+	// create a new handler
+	handler := HttpHandler{}
+	// listen and serve
+	http.ListenAndServe(":9000", handler)
+}
diff --git a/rapps/utils/generatejwt/generatejwt.go b/rapps/utils/generatejwt/generatejwt.go
new file mode 100644
index 0000000..666be24
--- /dev/null
+++ b/rapps/utils/generatejwt/generatejwt.go
@@ -0,0 +1,134 @@
+package generatejwt 
+
+import (
+	"fmt"
+	"github.com/dgrijalva/jwt-go"
+	"io/ioutil"
+	"log"
+	"time"
+)
+
+type JWT struct {
+	privateKey []byte
+	publicKey  []byte
+}
+
+func NewJWT(privateKey []byte, publicKey []byte) JWT {
+	return JWT{
+		privateKey: privateKey,
+		publicKey:  publicKey,
+	}
+}
+
+func readFile(file string) []byte {
+	key, err := ioutil.ReadFile(file)
+	if err != nil {
+		log.Fatalln(err)
+	}
+	return key
+}
+
+func (j JWT) createWithKey(ttl time.Duration, content interface{}, client , realm string) (string, error) {
+	key, err := jwt.ParseRSAPrivateKeyFromPEM(j.privateKey)
+	if err != nil {
+		return "", fmt.Errorf("create: parse key: %w", err)
+	}
+
+	now := time.Now().UTC()
+
+	claims := make(jwt.MapClaims)
+	claims["dat"] = content             // Our custom data.
+	claims["exp"] = now.Add(ttl).Unix() // The expiration time after which the token must be disregarded.
+	claims["iat"] = now.Unix()          // The time at which the token was issued.
+	claims["nbf"] = now.Unix()          // The time before which the token must be disregarded.
+	claims["jti"] = "myJWTId" + fmt.Sprint(now.UnixNano())
+	claims["sub"] = client //"jwtkey"
+	claims["iss"] = client //"jwtkey"
+	claims["aud"] = realm //"https://192.168.49.2:31561/auth/realms/jwtrealm"
+
+	token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
+	//token.Header["kid"] = "AKAwbsKtqu9OmIwIsPOUf5zTJkIC73hzY9Myv4srjTs"
+	tokenString, err := token.SignedString(key)
+	if err != nil {
+		return "", fmt.Errorf("create: sign token: %w", err)
+	}
+
+	return tokenString, nil
+}
+
+func createWithSecret(ttl time.Duration, content interface{}, secret string) (string, error) {
+	now := time.Now().UTC()
+
+	claims := make(jwt.MapClaims)
+	claims["dat"] = content             // Our custom data.
+	claims["exp"] = now.Add(ttl).Unix() // The expiration time after which the token must be disregarded.
+	claims["iat"] = now.Unix()          // The time at which the token was issued.
+	claims["nbf"] = now.Unix()          // The time before which the token must be disregarded.
+	claims["jti"] = "myJWTId" + fmt.Sprint(now.UnixNano())
+	claims["sub"] = "jwtsecret"
+	claims["iss"] = "jwtsecret"
+	claims["aud"] = "https://192.168.49.2:31561/auth/realms/jwtrealm"
+
+	token, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(secret))
+	if err != nil {
+		return "", fmt.Errorf("create: sign token: %w", err)
+	}
+
+	return token, nil
+}
+
+func (j JWT) Validate(token string) (interface{}, error) {
+        key, err := jwt.ParseRSAPublicKeyFromPEM(j.publicKey)
+        if err != nil {
+                return "", fmt.Errorf("validate: parse key: %w", err)
+        }
+
+        tok, err := jwt.Parse(token, func(jwtToken *jwt.Token) (interface{}, error) {
+                if _, ok := jwtToken.Method.(*jwt.SigningMethodRSA); !ok {
+                        return nil, fmt.Errorf("unexpected method: %s", jwtToken.Header["alg"])
+                }
+
+                return key, nil
+        })
+        if err != nil {
+                return nil, fmt.Errorf("validate: %w", err)
+        }
+
+        claims, ok := tok.Claims.(jwt.MapClaims)
+        if !ok || !tok.Valid {
+                return nil, fmt.Errorf("validate: invalid")
+        }
+
+        return claims["dat"], nil
+}
+
+
+func CreateJWT(privateKeyFile, publicKeyFile, secret, client, realm string) string {
+	if secret == "" {
+		prvKey := readFile(privateKeyFile)
+		pubKey := readFile(publicKeyFile) 
+
+		jwtToken := NewJWT(prvKey, pubKey)
+
+		// 1. Create a new JWT token.
+		tok, err := jwtToken.createWithKey(time.Hour, "Can be anything", client, realm)
+		if err != nil {
+			log.Fatalln(err)
+		}
+
+		// 2. Validate an existing JWT token.
+		_, err = jwtToken.Validate(tok)
+		if err != nil {
+			log.Fatalln(err)
+		}
+        return tok 		
+	} else {
+		// 1. Create a new JWT token.
+		tok, err := createWithSecret(time.Hour, "Can be anything", secret)
+		if err != nil {
+			log.Fatalln(err)
+		}
+        return tok 		
+	}
+
+}
diff --git a/rapps/utils/pemtojwks/pemtojwks.go b/rapps/utils/pemtojwks/pemtojwks.go
new file mode 100644
index 0000000..6592916
--- /dev/null
+++ b/rapps/utils/pemtojwks/pemtojwks.go
@@ -0,0 +1,115 @@
+package pemtojwks 
+
+import (
+	"crypto/rsa"
+	"crypto/sha1"
+	"crypto/x509"
+	"encoding/base64"
+	"encoding/json"
+	"encoding/pem"
+	"fmt"
+	"golang.org/x/crypto/ssh"
+	"io/ioutil"
+	"math/big"
+)
+
+type Jwks struct {
+	Keys []Key `json:"keys"`
+}
+type Key struct {
+	Kid string `json:"kid,omitempty"`
+	Kty string `json:"kty"`
+	Use string `json:"use"`
+	N   string `json:"n"`
+	E   string `json:"e"`
+	X5c []string `json:"x5c"`
+	X5t string `json:"x5t"`
+}
+
+func getKeyFromPrivate(key []byte) *rsa.PublicKey {
+	parsed, err := ssh.ParseRawPrivateKey(key)
+	if err != nil {
+		fmt.Println(err)
+	}
+
+	// Convert back to an *rsa.PrivateKey
+	privateKey := parsed.(*rsa.PrivateKey)
+
+	publicKey := &privateKey.PublicKey
+	return publicKey
+}
+
+func getKeyFromPublic(key []byte) *rsa.PublicKey {
+	pubPem, _ := pem.Decode(key)
+
+	parsed, err := x509.ParsePKIXPublicKey(pubPem.Bytes)
+	if err != nil {
+		fmt.Println("Unable to parse RSA public key", err)
+	}
+
+	// Convert back to an *rsa.PublicKey
+	publicKey := parsed.(*rsa.PublicKey)
+
+	return publicKey
+}
+
+func getCert(cert []byte) *x509.Certificate {
+	certPem, _ := pem.Decode(cert)
+	if certPem == nil {
+		panic("Failed to parse pem file")
+	}
+
+	// pass cert bytes
+	certificate, err := x509.ParseCertificate(certPem.Bytes)
+	if err != nil {
+		fmt.Println("Unable to parse Certificate", err)
+	}
+
+	return certificate
+}
+
+func CreateJWKS(keyFile, keyType, certFile string) string {
+	key, err := ioutil.ReadFile(keyFile)
+	if err != nil {
+		fmt.Println(err)
+	}
+
+	var publicKey *rsa.PublicKey
+
+	if keyType == "public" {
+		publicKey = getKeyFromPublic(key)
+	} else {
+		publicKey = getKeyFromPrivate(key)
+	}
+
+	cert, err := ioutil.ReadFile(certFile)
+	if err != nil {
+		fmt.Println(err)
+	}
+
+	certificate := getCert(cert)
+	// generate fingerprint with sha1
+	// you can also use md5, sha256, etc.
+	fingerprint := sha1.Sum(certificate.Raw)
+
+
+	jwksKey := Key{
+		Kid: "SIGNING_KEY",
+		Kty: "RSA",
+		Use: "sig",
+		N: base64.RawStdEncoding.EncodeToString(publicKey.N.Bytes()),
+		E: base64.RawStdEncoding.EncodeToString(big.NewInt(int64(publicKey.E)).Bytes()),
+		X5c: []string{base64.RawStdEncoding.EncodeToString(certificate.Raw)},
+		X5t: base64.RawStdEncoding.EncodeToString(fingerprint[:]),
+	}
+	jwksKeys := []Key{jwksKey}
+	jwks := Jwks{jwksKeys}
+
+	jwksJson, err := json.Marshal(jwks)
+	if err != nil {
+		fmt.Println(err)
+		return err.Error() 
+	}
+	return string(jwksJson)
+
+}