feat(apps): forte-drop web + mcp argocd apps (prod) #18
37
apps/base/forte-drop-mcp/forte-drop-mcp.yaml
Normal file
@@ -0,0 +1,37 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: forte-drop-mcp
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "1"
|
||||
notifications.argoproj.io/subscribe.on-sync-succeeded.slack: ""
|
||||
notifications.argoproj.io/subscribe.on-sync-failed.slack: ""
|
||||
notifications.argoproj.io/subscribe.on-degraded.slack: ""
|
||||
labels:
|
||||
app.kubernetes.io/name: forte-drop-mcp
|
||||
app.kubernetes.io/part-of: apps
|
||||
app.kubernetes.io/managed-by: argocd
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: default
|
||||
sources:
|
||||
- repoURL: ssh://git@git.forteapps.net:2222/Forte/forte-helm.git
|
||||
path: forteapp
|
||||
targetRevision: HEAD
|
||||
|
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/forte-drop-mcp/values.yaml
|
||||
- repoURL: ssh://git@git.forteapps.net:2222/Forte/helm-prod-values.git
|
||||
targetRevision: HEAD
|
||||
|
gitea_admin
commented
Using 'HEAD' for targetRevision can lead to unpredictable deployments - prefer a specific tag or branch. #ai-review-inline Using 'HEAD' for targetRevision can lead to unpredictable deployments - prefer a specific tag or branch.
```suggestion
targetRevision: main
```
#ai-review-inline
|
||||
ref: values
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: forte-drop
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
27
apps/base/forte-drop-mcp/keycloak-client-forte-drop-mcp.yaml
Normal file
@@ -0,0 +1,27 @@
|
||||
# MCP audience client. RFC 7591 dynamic-registration capable MCP clients (e.g.,
|
||||
# Claude Desktop) discover this via /.well-known/oauth-protected-resource and
|
||||
# request tokens with aud=https://mcp.drop-k8s.hackathon.forteapps.net/mcp.
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
|
gitea_admin
commented
Plain-text Secret should not be committed to Git - use SealedSecret or the Keycloak client registrar instead. #ai-review-inline Plain-text Secret should not be committed to Git - use SealedSecret or the Keycloak client registrar instead.
#ai-review-inline
|
||||
metadata:
|
||||
name: keycloak-client-forte-drop-mcp
|
||||
namespace: forte-drop
|
||||
labels:
|
||||
keycloak.forteapps.net/client-config: "true"
|
||||
stringData:
|
||||
client.json: |
|
||||
{
|
||||
|
danijel.simeunovic
commented
Du trenger ikke en egen klient for mcp server. Det lages automatisk når du legger inn Du trenger ikke en egen klient for mcp server. Det lages automatisk når du legger inn `auth.type: mcp` i helm values. Så denne kan slettes.
|
||||
"clientId": "forte-drop-mcp",
|
||||
"name": "Forte Drop (MCP)",
|
||||
"enabled": true,
|
||||
"protocol": "openid-connect",
|
||||
"clientAuthenticatorType": "client-secret",
|
||||
|
gitea_admin
commented
Client secret authentication requires a secret value which should not be stored in plain-text Git. #ai-review-inline Client secret authentication requires a secret value which should not be stored in plain-text Git.
#ai-review-inline
|
||||
"standardFlowEnabled": false,
|
||||
"directAccessGrantsEnabled": false,
|
||||
"serviceAccountsEnabled": false,
|
||||
"publicClient": false,
|
||||
"defaultClientScopes": ["openid","profile","email"],
|
||||
"attributes": {
|
||||
"access.token.lifespan": "3600"
|
||||
}
|
||||
}
|
||||
6
apps/base/forte-drop-mcp/kustomization.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- forte-drop-mcp.yaml
|
||||
- keycloak-client-forte-drop-mcp.yaml
|
||||
# - auth-oidc-sealed.yaml # added in follow-up commit
|
||||
37
apps/base/forte-drop/forte-drop.yaml
Normal file
@@ -0,0 +1,37 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: forte-drop
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "1"
|
||||
notifications.argoproj.io/subscribe.on-sync-succeeded.slack: ""
|
||||
notifications.argoproj.io/subscribe.on-sync-failed.slack: ""
|
||||
notifications.argoproj.io/subscribe.on-degraded.slack: ""
|
||||
labels:
|
||||
app.kubernetes.io/name: forte-drop
|
||||
app.kubernetes.io/part-of: apps
|
||||
app.kubernetes.io/managed-by: argocd
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: default
|
||||
sources:
|
||||
- repoURL: ssh://git@git.forteapps.net:2222/Forte/forte-helm.git
|
||||
path: forteapp
|
||||
targetRevision: HEAD
|
||||
|
gitea_admin
commented
Using 'HEAD' as targetRevision can lead to unstable deployments; prefer a specific tag, branch, or commit hash for better control. #ai-review-inline Using 'HEAD' as targetRevision can lead to unstable deployments; prefer a specific tag, branch, or commit hash for better control.
```suggestion
targetRevision: main
```
#ai-review-inline
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/forte-drop/values.yaml
|
||||
- repoURL: ssh://git@git.forteapps.net:2222/Forte/helm-prod-values.git
|
||||
targetRevision: HEAD
|
||||
|
gitea_admin
commented
Using 'HEAD' as targetRevision can lead to unstable deployments; prefer a specific tag, branch, or commit hash for better control. #ai-review-inline Using 'HEAD' as targetRevision can lead to unstable deployments; prefer a specific tag, branch, or commit hash for better control.
```suggestion
targetRevision: main
```
#ai-review-inline
|
||||
ref: values
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: forte-drop
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
28
apps/base/forte-drop/keycloak-client-forte-drop.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
# Labeled config Secret read by the Keycloak Client Registrar. The registrar will
|
||||
# create the OIDC client in the forte realm and write the resulting credentials
|
||||
# back into forte-drop-oidc-credentials Secret in this namespace within ~2 min.
|
||||
# That client-secret then gets manually copied into the auth-oidc SealedSecret
|
||||
# (one-time per cluster; see PR description).
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: keycloak-client-forte-drop
|
||||
namespace: forte-drop
|
||||
labels:
|
||||
keycloak.forteapps.net/client-config: "true"
|
||||
stringData:
|
||||
client.json: |
|
||||
{
|
||||
"clientId": "forte-drop",
|
||||
"name": "Forte Drop (web)",
|
||||
"enabled": true,
|
||||
"protocol": "openid-connect",
|
||||
"clientAuthenticatorType": "client-secret",
|
||||
"standardFlowEnabled": true,
|
||||
"directAccessGrantsEnabled": false,
|
||||
"serviceAccountsEnabled": false,
|
||||
"publicClient": false,
|
||||
"redirectUris": ["https://drop-k8s.hackathon.forteapps.net/auth/callback"],
|
||||
|
gitea_admin
commented
Hardcoded URL in redirectUris should be templated or moved to values to support different environments. #ai-review-inline Hardcoded URL in redirectUris should be templated or moved to values to support different environments.
#ai-review-inline
|
||||
"webOrigins": ["https://drop-k8s.hackathon.forteapps.net"],
|
||||
|
gitea_admin
commented
Hardcoded URL in webOrigins should be templated or moved to values to support different environments. #ai-review-inline Hardcoded URL in webOrigins should be templated or moved to values to support different environments.
#ai-review-inline
|
||||
"defaultClientScopes": ["openid","email","profile"]
|
||||
}
|
||||
|
danijel.simeunovic
commented
Det du mangler her er hvor du vil at Det du mangler her er hvor du vil at `Secret` skal bli laget og med hvilke keys:
```
"secret": {
"namespace": "myapp", # Where to create credential Secret
"name": "myapp-oidc-credentials", # Name of credential Secret
"keys": {
"clientId": "client-id", # Key name for client ID
"clientSecret": "client-secret" # Key name for client secret
}
}
```
danijel.simeunovic
commented
Detaljert forklaring, hvis interessant: What a Developer Should PushA developer deploying a new application must create a Config Secret in their application's namespace. This Secret contains the desired Keycloak client configuration. The FormatKubernetes Secret YAML: How It Works: Client ID vs Client SecretClient ID (Pre-determined by Developer):
Client Secret (Generated by Keycloak):
Developer Workflow
Change DetectionThe CronJob computes a SHA256 hash of Detaljert forklaring, hvis interessant:
### What a Developer Should Push
A developer deploying a new application must create a __Config Secret__ in their application's namespace. This Secret contains the desired Keycloak client configuration.
### The Format
__Kubernetes Secret YAML:__
```yaml
apiVersion: v1
kind: Secret
metadata:
name: keycloak-client-<app-name> # Any descriptive name
namespace: <app-namespace> # Your app's namespace (e.g., "myapp")
labels:
keycloak.forteapps.net/client-config: "true" # REQUIRED - triggers processing
annotations:
keycloak.forteapps.net/source-namespace: "myapp" # Optional - tracks ownership
stringData:
client.json: | # REQUIRED - the client configuration
{
"clientId": "myapp", # Pre-determined by developer
"name": "My Application", # Display name in Keycloak UI
"redirectUris": ["https://myapp.forteapps.net/*"],
"webOrigins": ["https://myapp.forteapps.net"],
"defaultClientScopes": ["openid", "email", "profile"],
"protocolMappers": [],
"secret": {
"namespace": "myapp", # Where to create credential Secret
"name": "myapp-oidc-credentials", # Name of credential Secret
"keys": {
"clientId": "client-id", # Key name for client ID
"clientSecret": "client-secret" # Key name for client secret
}
}
}
```
### How It Works: Client ID vs Client Secret
__Client ID (Pre-determined by Developer):__
- The developer chooses `clientId` (e.g., `"myapp"`)
- This becomes the public identifier for the OIDC client
- Used in OAuth flows, login redirects, token requests
- Stored in the credential Secret under the specified key (default: `client-id`)
__Client Secret (Generated by Keycloak):__
- Keycloak auto-generates a cryptographically secure secret
- The CronJob fetches it via: `GET /admin/realms/forte/clients/{uuid}/client-secret`
- The CronJob then creates/updates __TWO__ Secrets:
__1. Target Namespace Secret__ (`myapp/myapp-oidc-credentials`):
```yaml
apiVersion: v1
kind: Secret
metadata:
name: myapp-oidc-credentials
namespace: myapp
labels:
app.kubernetes.io/managed-by: keycloak-client-registrar
type: Opaque
data:
client-id: bXlhcHA= # base64("myapp") - from client.json
client-secret: <base64> # Generated by Keycloak
```
__2. Central Backup Secret__ (`secrets/myapp-oidc-credentials`):
- Identical content
- Always created even if target namespace doesn't exist
- Used for external deployments, disaster recovery, auditing
### Developer Workflow
1. __Create Config Secret__ in your Helm chart or Kustomize (in your app's namespace)
2. __Deploy__ - Kyverno policy clones it to `keycloak` namespace automatically
3. __Wait__ - CronJob picks it up within 2 minutes
4. __Reference__ the generated credential Secret in your Deployment:
```yaml
env:
- name: OIDC_CLIENT_ID
valueFrom:
secretKeyRef:
name: myapp-oidc-credentials
key: client-id
- name: OIDC_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: myapp-oidc-credentials
key: client-secret
```
### Change Detection
The CronJob computes a SHA256 hash of `client.json`. If unchanged and credential Secret exists, it skips processing. To force re-sync, modify any field in `client.json` (e.g., add trailing space to `name`).
|
||||
7
apps/base/forte-drop/kustomization.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- forte-drop.yaml
|
||||
- keycloak-client-forte-drop.yaml
|
||||
# - forte-drop-secrets-sealed.yaml # added in follow-up commit
|
||||
# - auth-oidc-sealed.yaml # added in follow-up commit (after Keycloak registrar creates client_secret)
|
||||
@@ -6,3 +6,5 @@ resources:
|
||||
- musicman
|
||||
- ts-mcp
|
||||
- argo-mcp
|
||||
- forte-drop
|
||||
- forte-drop-mcp
|
||||
|
||||
Using 'HEAD' for targetRevision can lead to unpredictable deployments - prefer a specific tag or branch.
#ai-review-inline