Skip to main content

Security & Access Control

Security in DevOpsGenie is architectural, not operational. This guide covers the security layers that are applied by default and how to configure them for your environment.

Security Architecture Overview

DevOpsGenie applies security controls at five layers:

┌────────────────────────────────────────────────────────┐
5. Supply Chain Image scanning, SBOM, Sigstore │
├────────────────────────────────────────────────────────┤
4. Runtime Falco, Pod Security Standards │
├────────────────────────────────────────────────────────┤
3. Workload RBAC, Network Policies, mTLS │
├────────────────────────────────────────────────────────┤
2. Identity IRSA, OIDC, cert-manager │
├────────────────────────────────────────────────────────┤
1. Infrastructure VPC, IAM least-privilege, KMS │
└────────────────────────────────────────────────────────┘

IAM & IRSA

IAM Roles for Service Accounts (IRSA)

Never use EC2 instance profiles for pod-level AWS access. Each service that needs AWS permissions gets its own IAM role bound to its Kubernetes ServiceAccount via IRSA.

terraform/iam/service-accounts.tf
module "payments_api_irsa" {
source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"

role_name = "payments-api-${var.environment}"

oidc_providers = {
cluster = {
provider_arn = var.oidc_provider_arn
namespace_service_accounts = ["team-payments:payments-api"]
}
}

# Inline policy — only the exact permissions this service needs
role_policy_arns = {
policy = aws_iam_policy.payments_api.arn
}
}

resource "aws_iam_policy" "payments_api" {
name = "payments-api-${var.environment}"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret"
]
Resource = [
"arn:aws:secretsmanager:us-east-1:${var.account_id}:secret:/payments/*"
]
},
{
Effect = "Allow"
Action = ["s3:GetObject", "s3:PutObject"]
Resource = ["arn:aws:s3:::payments-receipts-${var.environment}/*"]
}
]
})
}
kubernetes/apps/payments-api-serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: payments-api
namespace: team-payments
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::ACCOUNT_ID:role/payments-api-production
eks.amazonaws.com/token-expiration: "86400"
automountServiceAccountToken: true

Pod Security Standards

DevOpsGenie enforces Pod Security Standards at the namespace level. All production namespaces use restricted by default:

kubernetes/namespaces/team-payments.yaml
apiVersion: v1
kind: Namespace
metadata:
name: team-payments
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted

The restricted profile enforces:

  • runAsNonRoot: true
  • allowPrivilegeEscalation: false
  • seccompProfile.type: RuntimeDefault
  • No hostPath, hostNetwork, hostPID, or hostIPC
  • Capabilities dropped to ALL

OPA Gatekeeper Policies

DevOpsGenie includes a CIS-aligned policy bundle deployed via OPA Gatekeeper:

Require Resource Limits

kubernetes/policies/gatekeeper/require-resource-limits.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredResources
metadata:
name: require-resource-limits
spec:
match:
kinds:
- apiGroups: ["apps"]
kinds: ["Deployment", "StatefulSet", "DaemonSet"]
excludedNamespaces:
- kube-system
- karpenter
- monitoring
parameters:
limits: ["cpu", "memory"]
requests: ["cpu", "memory"]

Restrict Image Registries

kubernetes/policies/gatekeeper/allowed-registries.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRepos
metadata:
name: allowed-registries
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
parameters:
repos:
- "ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com"
- "public.ecr.aws/eks-distro"
- "registry.k8s.io"

Secrets Management

External Secrets Operator

Sync secrets from AWS Secrets Manager into Kubernetes Secrets automatically:

kubernetes/secrets/payments-db.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: payments-db-credentials
namespace: team-payments
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secretsmanager
kind: ClusterSecretStore
target:
name: payments-db-credentials
creationPolicy: Owner
deletionPolicy: Retain
template:
engineVersion: v2
data:
username: "{{ .username }}"
password: "{{ .password }}"
connection-string: "postgresql://{{ .username }}:{{ .password }}@{{ .host }}:5432/{{ .dbname }}"
data:
- secretKey: username
remoteRef:
key: /payments/production/database
property: username
- secretKey: password
remoteRef:
key: /payments/production/database
property: password
- secretKey: host
remoteRef:
key: /payments/production/database
property: host
- secretKey: dbname
remoteRef:
key: /payments/production/database
property: dbname

Runtime Threat Detection with Falco

Falco monitors kernel-level syscalls and generates alerts when suspicious behavior is detected:

kubernetes/security/falco-rules.yaml
customRules:
devopsgenie-rules.yaml: |-
- rule: Write to sensitive files
desc: Detect writes to sensitive host filesystem paths
condition: >
open_write and
(fd.name startswith /etc or
fd.name startswith /usr/bin or
fd.name startswith /usr/sbin) and
not proc.name in (allowed_writers)
output: >
Sensitive file write detected (user=%user.name file=%fd.name
proc=%proc.name parent=%proc.pname container=%container.id)
priority: CRITICAL
tags: [filesystem, cis]

- rule: Shell in container
desc: Alert when a shell is spawned in a production container
condition: >
spawned_process and
container and
not container.image.repository in (allowed_images) and
proc.name in (shell_binaries)
output: >
Shell spawned in container (user=%user.name container=%container.id
image=%container.image.repository shell=%proc.name)
priority: WARNING
tags: [container, shell]

TLS & Certificate Management

cert-manager automates TLS certificate issuance and renewal:

kubernetes/certificates/payments-api.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: payments-api-tls
namespace: team-payments
spec:
secretName: payments-api-tls
duration: 2160h # 90 days
renewBefore: 360h # renew 15 days before expiry
issuerRef:
name: letsencrypt-production
kind: ClusterIssuer
dnsNames:
- payments.example.com
- api.payments.example.com

Security Scanning

Container Image Scanning

All images are scanned with Trivy in the CI pipeline (see CI/CD Best Practices) and continuously in the cluster with Trivy Operator:

# Manually scan an image
trivy image --severity CRITICAL,HIGH \
ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/payments-api:latest

# View in-cluster vulnerability reports
kubectl get vulnerabilityreports -n team-payments

Compliance Audit

Run a full compliance audit against CIS Kubernetes Benchmark:

devopsgenie security audit --benchmark cis-kubernetes-v1.8
devopsgenie security audit --benchmark aws-well-architected

Next Steps