AppProjects
Project Anatomy
An AppProject defines a security boundary in Argo CD. Every Application belongs to a project (default if unspecified). Projects control three things: which Git repos are allowed as sources, which clusters/namespaces are allowed as destinations, and which Kubernetes resource types can be deployed:
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: grade-api-project
namespace: argocd
spec:
description: "Project for the Grade API team"
# ── SOURCE RESTRICTIONS ─────────────────────────────────────────
sourceRepos:
- "https://github.com/your-org/grade-api-gitops"
- "https://github.com/your-org/grade-api"
- "oci://ghcr.io/your-org/charts"
# Use '*' to allow all repos (not recommended for production)
# ── DESTINATION RESTRICTIONS ────────────────────────────────────
destinations:
- server: "https://kubernetes.default.svc"
namespace: "grade-api-dev"
- server: "https://kubernetes.default.svc"
namespace: "grade-api-staging"
- server: "https://prod-cluster.example.com"
namespace: "production"
# Wildcard namespaces allowed:
- server: "https://kubernetes.default.svc"
namespace: "grade-api-*"
# ── RESOURCE ALLOW/DENY LISTS ───────────────────────────────────
# If clusterResourceWhitelist is empty, no cluster-scoped resources allowed
clusterResourceWhitelist:
- group: ""
kind: Namespace # Allow creating Namespaces
- group: "rbac.authorization.k8s.io"
kind: ClusterRoleBinding # Allow CRBs for service accounts
# Namespace-scoped resources allowed (if empty, all allowed)
namespaceResourceWhitelist:
- group: "*"
kind: "*" # Allow all namespace-scoped resources
# Or: block specific resource types
namespaceResourceBlacklist:
- group: ""
kind: ResourceQuota # Teams can't create/modify ResourceQuotas
- group: ""
kind: LimitRange
# ── ROLES ────────────────────────────────────────────────────────
roles:
- name: developer
description: "Grade API developers"
policies:
- p, proj:grade-api-project:developer, applications, get, grade-api-project/*, allow
- p, proj:grade-api-project:developer, applications, sync, grade-api-project/*, allow
- p, proj:grade-api-project:developer, applications, action/*, grade-api-project/*, allow
groups:
- "github.com:grade-api-team" # GitHub team mapping
- name: readonly
description: "Read-only access for stakeholders"
policies:
- p, proj:grade-api-project:readonly, applications, get, grade-api-project/*, allow
groups:
- "github.com:grade-api-stakeholders"
# ── SYNC WINDOWS (review from Part 2) ────────────────────────────
syncWindows:
- kind: allow
schedule: "0 9 * * 1-5"
duration: 8h
applications:
- "*"
manualSync: true
# ── ORPHANED RESOURCE MONITORING ─────────────────────────────────
# Alert when namespaced resources exist that Argo CD doesn't manage
orphanedResources:
warn: true # Warn (don't sync fail) for unmanaged resources
ignore:
- group: ""
kind: ConfigMap
name: kube-root-ca.crt # Ignore auto-injected CMs
Source Repo Restrictions
# Examples of sourceRepos patterns
# Allow only from your organization:
sourceRepos:
- "https://github.com/your-org/*" # All repos in org (glob)
# Allow specific OCI charts:
sourceRepos:
- "oci://ghcr.io/your-org/*"
# Block all except one repo (strict):
sourceRepos:
- "https://github.com/your-org/grade-api-gitops"
# Helm chart repos:
sourceRepos:
- "https://charts.bitnami.com/bitnami"
- "https://prometheus-community.github.io/helm-charts"
RBAC
The RBAC Model
Argo CD RBAC uses a Casbin policy format. Policies are stored in the argocd-rbac-cm ConfigMap and define what subjects (users, groups, or roles) can do on which resources:
# RBAC policy syntax:
# p, <subject>, <resource>, <action>, <object>, <effect>
# Examples:
# Allow role 'developer' to sync any application in 'my-project':
p, role:developer, applications, sync, my-project/*, allow
# Allow GitHub user 'alice' to get all applications:
p, alice, applications, get, */*, allow
# Allow GitHub team to create applications in specific project:
p, role:team-lead, applications, create, grade-api-project/*, allow
# Deny sync to production for developers (deny takes precedence):
p, role:developer, applications, sync, */production-*, deny
Roles & Policies
# argocd-rbac-cm ConfigMap — global RBAC policies
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-rbac-cm
namespace: argocd
data:
# Default policy for unauthenticated/unmatched users
policy.default: role:readonly
# CSV-formatted policy rules
policy.csv: |
# ── Platform team: full admin ────────────────────────────────
p, role:platform-admin, *, *, */*, allow
# ── Developer role: can deploy but not delete cluster resources ─
p, role:developer, applications, get, */*, allow
p, role:developer, applications, sync, */*, allow
p, role:developer, applications, action/*, */*, allow
p, role:developer, logs, get, */*, allow
p, role:developer, exec, create, */*, allow
# ── Operator role: can sync and manage, but not create/delete apps ─
p, role:operator, applications, get, */*, allow
p, role:operator, applications, sync, */*, allow
p, role:operator, applications, action/*, */*, allow
# ── Readonly role: view only ──────────────────────────────────
p, role:readonly, applications, get, */*, allow
p, role:readonly, repositories, get, *, allow
# ── Group to role bindings ────────────────────────────────────
g, github.com:platform-team, role:platform-admin
g, github.com:backend-developers, role:developer
g, github.com:sre-team, role:operator
# OIDC scopes to include in token (for group membership)
scopes: "[groups, email]"
Built-in Roles
- role:admin — Full access to everything. Only the Argo CD admin account has this by default.
- role:readonly — Read-only access to all Applications and their status. Good default for stakeholders.
policy.default: role:readonly to ensure unauthenticated users and unmatched authenticated users get read-only access, not no access (which might default to admin in some configurations). Never set policy.default: role:admin in production.
SSO Integration
GitHub SSO
# argocd-cm — configure Dex for GitHub OAuth
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cm
namespace: argocd
data:
# URL where Argo CD is accessible
url: https://argocd.company.com
# Dex OIDC configuration
dex.config: |
connectors:
- type: github
id: github
name: GitHub
config:
clientID: $github-client-id # from Secret 'argocd-secret'
clientSecret: $github-client-secret
# Redirect users here after GitHub auth
redirectURI: https://argocd.company.com/api/dex/callback
# Restrict to specific GitHub org
orgs:
- name: your-org
teams:
- platform-team
- backend-developers
- sre-team
# Load group membership into the token
loadAllGroups: false # only load teams listed above
useLoginAsID: false
# Create GitHub OAuth App at: github.com/organizations/your-org/settings/applications
# Homepage URL: https://argocd.company.com
# Authorization callback URL: https://argocd.company.com/api/dex/callback
# Store credentials in argocd-secret
kubectl create secret generic argocd-github-sso \
--from-literal=github-client-id=YOUR_CLIENT_ID \
--from-literal=github-client-secret=YOUR_CLIENT_SECRET \
-n argocd
# Patch argocd-secret to include the github credentials
# (Or manage via external-secrets-operator — see External Secrets deep-dive)
Group-to-Role Mapping
# After GitHub SSO setup, groups appear as:
# github.com:your-org:team-name
# In argocd-rbac-cm:
g, github.com:your-org:platform-team, role:platform-admin
g, github.com:your-org:backend-developers, role:developer
g, github.com:your-org:sre-team, role:operator
# Verify your user's groups after login:
argocd account get-user-info
# Name: alice
# Email: alice@company.com
# Groups: [github.com:your-org:backend-developers]
# Check if a user would be allowed an action:
argocd admin settings rbac can \
alice applications sync \
grade-api-project/grade-api-prod
# YES (allowed)
Declarative User Management
# argocd-cm — define local accounts (for CI/CD pipelines, not humans)
# Use SSO for human users; local accounts for service accounts
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cm
namespace: argocd
data:
url: https://argocd.company.com
# CI pipeline service account (can only sync specific projects)
accounts.ci-pipeline: apiKey
accounts.ci-pipeline.enabled: "true"
# Read-only monitoring account for observability tools
accounts.grafana-argocd: apiKey
accounts.grafana-argocd.enabled: "true"
dex.config: |
# ... SSO config above ...
# argocd-rbac-cm — policies for local accounts
data:
policy.csv: |
# CI pipeline can sync but not create/delete apps
p, ci-pipeline, applications, sync, */*, allow
p, ci-pipeline, applications, get, */*, allow
# Grafana can only read (for Argo CD metrics)
p, grafana-argocd, applications, get, */*, allow
p, grafana-argocd, projects, get, *, allow
# Generate API token for CI pipeline
argocd account generate-token \
--account ci-pipeline \
--expires-in 8760h # 1 year
# Use token in CI (GitHub Actions secret, GitLab variable, etc.)
# ARGOCD_TOKEN: <token from above>
# CI pipeline login
argocd login argocd.company.com \
--auth-token $ARGOCD_TOKEN \
--grpc-web
# Sync from CI
argocd app sync grade-api-prod \
--auth-token $ARGOCD_TOKEN \
--timeout 120
Exercises
readonly-user with only get permissions. Create an API token for it. Try to sync an Application using that token — verify you get a permission denied error. Try argocd app list — verify it succeeds.
argocd admin settings rbac can to verify the expected permissions without needing real users.
Key Takeaways & Next Steps
- AppProjects enforce tenancy: restrict which repos, clusters, namespaces, and resource types each team can use
- RBAC in argocd-rbac-cm uses Casbin CSV format:
p, <subject>, <resource>, <action>, <object>, <effect> - Set
policy.default: role:readonly— never role:admin - Use SSO (GitHub, Okta, Azure AD) for human users; local accounts only for CI pipelines and service accounts
- Group mappings:
g, github.com:org:team, role:my-rolemap GitHub teams to Argo CD roles - All RBAC config should be managed declaratively in ConfigMaps, never via the UI
Next in This Track
In Part 5: Notifications & Image Updater, we complete the GitOps loop — Argo CD notifications for Slack/Teams/email alerts on sync events, and the Argo CD Image Updater for automatic image tag promotion when new container images are published.