mirror of
https://github.com/holos-run/holos.git
synced 2026-03-19 00:37:45 +00:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a689c53a9c | ||
|
|
58cdda1d35 | ||
|
|
bcb02b5c5c | ||
|
|
0736c7de1a | ||
|
|
28be9f9fbb | ||
|
|
647681de38 | ||
|
|
81beb5c539 | ||
|
|
5c1e0a29c8 | ||
|
|
01ac5276a9 | ||
|
|
e40594ad8e | ||
|
|
bc9c6a622a | ||
|
|
17f22199b7 | ||
|
|
7e93fe4535 | ||
|
|
2e98df3572 | ||
|
|
3b561de413 | ||
|
|
0d0dae8742 | ||
|
|
61b4b5bd17 |
37
docs/examples/authpolicy.cue
Normal file
37
docs/examples/authpolicy.cue
Normal file
@@ -0,0 +1,37 @@
|
||||
package holos
|
||||
|
||||
import ap "security.istio.io/authorizationpolicy/v1"
|
||||
|
||||
// #AuthPolicyRules represents AuthorizationPolicy rules for hosts that need specialized treatment. Entries in this struct are exclused from the blank ingressauth AuthorizationPolicy governing the ingressgateway and included in a spcialized policy
|
||||
#AuthPolicyRules: {
|
||||
// AuthProxySpec represents the identity provider configuration
|
||||
AuthProxySpec: #AuthProxySpec & #Platform.authproxy
|
||||
|
||||
// Hosts are hosts that need specialized treatment
|
||||
hosts: {
|
||||
[Name=_]: {
|
||||
// name is the fully qualifed hostname, a Host: header value.
|
||||
name: Name
|
||||
// slug is the resource name prefix
|
||||
slug: string
|
||||
// Refer to https://istio.io/latest/docs/reference/config/security/authorization-policy/#Rule
|
||||
spec: ap.#AuthorizationPolicySpec & {
|
||||
action: "CUSTOM"
|
||||
provider: name: AuthProxySpec.provider
|
||||
selector: matchLabels: istio: "ingressgateway"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
objects: #APIObjects & {
|
||||
for Host in hosts {
|
||||
apiObjects: {
|
||||
AuthorizationPolicy: "\(Host.slug)-custom": {
|
||||
metadata: namespace: "istio-ingress"
|
||||
metadata: name: "\(Host.slug)-custom"
|
||||
spec: Host.spec
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,189 @@
|
||||
// Code generated by timoni. DO NOT EDIT.
|
||||
|
||||
//timoni:generate timoni vendor crd -f /home/jeff/workspace/holos-run/holos-infra/deploy/clusters/k2/components/prod-platform-argocd/prod-platform-argocd.gen.yaml
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import "strings"
|
||||
|
||||
// AppProject provides a logical grouping of applications,
|
||||
// providing controls for: * where the apps may deploy to
|
||||
// (cluster whitelist) * what may be deployed (repository
|
||||
// whitelist, resource whitelist/blacklist) * who can access
|
||||
// these applications (roles, OIDC group claims bindings) * and
|
||||
// what they can do (RBAC policies) * automation access to these
|
||||
// roles (JWT tokens)
|
||||
#AppProject: {
|
||||
// APIVersion defines the versioned schema of this representation
|
||||
// of an object. Servers should convert recognized schemas to the
|
||||
// latest internal value, and may reject unrecognized values.
|
||||
// More info:
|
||||
// https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
apiVersion: "argoproj.io/v1alpha1"
|
||||
|
||||
// Kind is a string value representing the REST resource this
|
||||
// object represents. Servers may infer this from the endpoint
|
||||
// the client submits requests to. Cannot be updated. In
|
||||
// CamelCase. More info:
|
||||
// https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
kind: "AppProject"
|
||||
metadata: {
|
||||
name!: strings.MaxRunes(253) & strings.MinRunes(1) & {
|
||||
string
|
||||
}
|
||||
namespace!: strings.MaxRunes(63) & strings.MinRunes(1) & {
|
||||
string
|
||||
}
|
||||
labels?: {
|
||||
[string]: string
|
||||
}
|
||||
annotations?: {
|
||||
[string]: string
|
||||
}
|
||||
}
|
||||
|
||||
// AppProjectSpec is the specification of an AppProject
|
||||
spec!: #AppProjectSpec
|
||||
}
|
||||
|
||||
// AppProjectSpec is the specification of an AppProject
|
||||
#AppProjectSpec: {
|
||||
// ClusterResourceBlacklist contains list of blacklisted cluster
|
||||
// level resources
|
||||
clusterResourceBlacklist?: [...{
|
||||
group: string
|
||||
kind: string
|
||||
}]
|
||||
|
||||
// ClusterResourceWhitelist contains list of whitelisted cluster
|
||||
// level resources
|
||||
clusterResourceWhitelist?: [...{
|
||||
group: string
|
||||
kind: string
|
||||
}]
|
||||
|
||||
// Description contains optional project description
|
||||
description?: string
|
||||
|
||||
// Destinations contains list of destinations available for
|
||||
// deployment
|
||||
destinations?: [...{
|
||||
// Name is an alternate way of specifying the target cluster by
|
||||
// its symbolic name. This must be set if Server is not set.
|
||||
name?: string
|
||||
|
||||
// Namespace specifies the target namespace for the application's
|
||||
// resources. The namespace will only be set for namespace-scoped
|
||||
// resources that have not set a value for .metadata.namespace
|
||||
namespace?: string
|
||||
|
||||
// Server specifies the URL of the target cluster's Kubernetes
|
||||
// control plane API. This must be set if Name is not set.
|
||||
server?: string
|
||||
}]
|
||||
|
||||
// NamespaceResourceBlacklist contains list of blacklisted
|
||||
// namespace level resources
|
||||
namespaceResourceBlacklist?: [...{
|
||||
group: string
|
||||
kind: string
|
||||
}]
|
||||
|
||||
// NamespaceResourceWhitelist contains list of whitelisted
|
||||
// namespace level resources
|
||||
namespaceResourceWhitelist?: [...{
|
||||
group: string
|
||||
kind: string
|
||||
}]
|
||||
|
||||
// OrphanedResources specifies if controller should monitor
|
||||
// orphaned resources of apps in this project
|
||||
orphanedResources?: {
|
||||
// Ignore contains a list of resources that are to be excluded
|
||||
// from orphaned resources monitoring
|
||||
ignore?: [...{
|
||||
group?: string
|
||||
kind?: string
|
||||
name?: string
|
||||
}]
|
||||
|
||||
// Warn indicates if warning condition should be created for apps
|
||||
// which have orphaned resources
|
||||
warn?: bool
|
||||
}
|
||||
|
||||
// PermitOnlyProjectScopedClusters determines whether destinations
|
||||
// can only reference clusters which are project-scoped
|
||||
permitOnlyProjectScopedClusters?: bool
|
||||
|
||||
// Roles are user defined RBAC roles associated with this project
|
||||
roles?: [...{
|
||||
// Description is a description of the role
|
||||
description?: string
|
||||
|
||||
// Groups are a list of OIDC group claims bound to this role
|
||||
groups?: [...string]
|
||||
|
||||
// JWTTokens are a list of generated JWT tokens bound to this role
|
||||
jwtTokens?: [...{
|
||||
exp?: int
|
||||
iat: int
|
||||
id?: string
|
||||
}]
|
||||
|
||||
// Name is a name for this role
|
||||
name: string
|
||||
|
||||
// Policies Stores a list of casbin formatted strings that define
|
||||
// access policies for the role in the project
|
||||
policies?: [...string]
|
||||
}]
|
||||
|
||||
// SignatureKeys contains a list of PGP key IDs that commits in
|
||||
// Git must be signed with in order to be allowed for sync
|
||||
signatureKeys?: [...{
|
||||
// The ID of the key in hexadecimal notation
|
||||
keyID: string
|
||||
}]
|
||||
|
||||
// SourceNamespaces defines the namespaces application resources
|
||||
// are allowed to be created in
|
||||
sourceNamespaces?: [...string]
|
||||
|
||||
// SourceRepos contains list of repository URLs which can be used
|
||||
// for deployment
|
||||
sourceRepos?: [...string]
|
||||
|
||||
// SyncWindows controls when syncs can be run for apps in this
|
||||
// project
|
||||
syncWindows?: [...{
|
||||
// Applications contains a list of applications that the window
|
||||
// will apply to
|
||||
applications?: [...string]
|
||||
|
||||
// Clusters contains a list of clusters that the window will apply
|
||||
// to
|
||||
clusters?: [...string]
|
||||
|
||||
// Duration is the amount of time the sync window will be open
|
||||
duration?: string
|
||||
|
||||
// Kind defines if the window allows or blocks syncs
|
||||
kind?: string
|
||||
|
||||
// ManualSync enables manual syncs when they would otherwise be
|
||||
// blocked
|
||||
manualSync?: bool
|
||||
|
||||
// Namespaces contains a list of namespaces that the window will
|
||||
// apply to
|
||||
namespaces?: [...string]
|
||||
|
||||
// Schedule is the time the window will begin, specified in cron
|
||||
// format
|
||||
schedule?: string
|
||||
|
||||
// TimeZone of the sync that will be applied to the schedule
|
||||
timeZone?: string
|
||||
}]
|
||||
}
|
||||
@@ -1,5 +1,21 @@
|
||||
package holos
|
||||
|
||||
#PlatformServers: {
|
||||
for cluster in #Platform.clusters {
|
||||
(cluster.name): {
|
||||
"https-istio-ingress-httpbin": {
|
||||
let cert = #PlatformCerts[cluster.name+"-httpbin"]
|
||||
hosts: [for host in cert.spec.dnsNames {"istio-ingress/\(host)"}]
|
||||
port: name: "https-istio-ingress-httpbin"
|
||||
port: number: 443
|
||||
port: protocol: "HTTPS"
|
||||
tls: credentialName: cert.spec.secretName
|
||||
tls: mode: "SIMPLE"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#PlatformCerts: {
|
||||
// Globally scoped platform services are defined here.
|
||||
login: #PlatformCert & {
|
||||
|
||||
@@ -4,4 +4,4 @@ package holos
|
||||
#InputKeys: project: "iam"
|
||||
|
||||
// Shared dependencies for all components in this collection.
|
||||
#DependsOn: namespaces: name: "\(#StageName)-secrets-namespaces"
|
||||
#DependsOn: namespaces: name: "\(#StageName)-secrets-stores"
|
||||
|
||||
@@ -37,7 +37,7 @@ spec: components: KubernetesObjectsList: [
|
||||
#KubernetesObjects & {
|
||||
metadata: name: "prod-iam-postgres"
|
||||
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
_dependsOn: "prod-secrets-stores": _
|
||||
_dependsOn: "prod-iam-postgres-certs": _
|
||||
apiObjectMap: OBJECTS.apiObjectMap
|
||||
},
|
||||
@@ -68,7 +68,7 @@ let OBJECTS = #APIObjects & {
|
||||
replicas: 2
|
||||
dataVolumeClaimSpec: {
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources: requests: storage: "10Gi"
|
||||
resources: requests: storage: "20Gi"
|
||||
}
|
||||
}]
|
||||
standby: {
|
||||
@@ -124,7 +124,7 @@ let OBJECTS = #APIObjects & {
|
||||
"\(BucketRepoName)-cipher-type": "aes-256-cbc"
|
||||
// "The convention we recommend for setting this variable is /pgbackrest/$NAMESPACE/$CLUSTER_NAME/repoN"
|
||||
// Ref: https://access.crunchydata.com/documentation/postgres-operator/latest/tutorials/backups-disaster-recovery/backups#understanding-backup-configuration-and-basic-operations
|
||||
"\(BucketRepoName)-path": "/pgbackrest/\(#TargetNamespace)/\(metadata.name)/\(manual.repoName)"
|
||||
"\(BucketRepoName)-path": "/pgbackrest/\(metadata.namespace)/\(metadata.name)/\(manual.repoName)"
|
||||
}
|
||||
repos: [
|
||||
{
|
||||
@@ -165,7 +165,7 @@ let HighlyAvailable = {
|
||||
replicas: 2
|
||||
dataVolumeClaimSpec: {
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources: requests: storage: string | *"10Gi"
|
||||
resources: requests: storage: string | *"20Gi"
|
||||
}
|
||||
affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: [{
|
||||
weight: 1
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package holos
|
||||
|
||||
#InstancePrefix: "prod-iam"
|
||||
#TargetNamespace: #InstancePrefix + "-zitadel"
|
||||
#InstancePrefix: "prod-iam"
|
||||
|
||||
// The namespace is managed by a project.
|
||||
#TargetNamespace: _Projects.iam.environments.prod.namespace
|
||||
|
||||
// _DBName is the database name used across multiple holos components in this project
|
||||
_DBName: "zitadel"
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package holos
|
||||
|
||||
import "encoding/yaml"
|
||||
|
||||
let ArgoCD = "argocd"
|
||||
let Namespace = "prod-platform"
|
||||
|
||||
spec: components: HelmChartList: [
|
||||
#HelmChart & {
|
||||
_dependsOn: "prod-secrets-stores": _
|
||||
namespace: Namespace
|
||||
|
||||
metadata: name: "\(namespace)-\(ArgoCD)"
|
||||
|
||||
chart: {
|
||||
name: "argo-cd"
|
||||
release: "argocd"
|
||||
version: "6.7.8"
|
||||
repository: {
|
||||
name: "argocd"
|
||||
url: "https://argoproj.github.io/argo-helm"
|
||||
}
|
||||
}
|
||||
|
||||
_values: #ArgoCDValues & {
|
||||
kubeVersionOverride: "1.29.0"
|
||||
global: domain: "argocd.\(#ClusterName).\(#Platform.org.domain)"
|
||||
dex: enabled: false
|
||||
// for integration with istio
|
||||
configs: params: "server.insecure": true
|
||||
configs: cm: {
|
||||
"admin.enabled": false
|
||||
"oidc.config": yaml.Marshal(OIDCConfig)
|
||||
}
|
||||
}
|
||||
|
||||
// Holos overlay objects
|
||||
apiObjectMap: OBJECTS.apiObjectMap
|
||||
},
|
||||
]
|
||||
|
||||
let OBJECTS = #APIObjects & {
|
||||
apiObjects: {
|
||||
// ExternalSecret: "deploy-key": _
|
||||
VirtualService: (ArgoCD): {
|
||||
metadata: name: ArgoCD
|
||||
metadata: namespace: Namespace
|
||||
spec: hosts: [
|
||||
ArgoCD + ".\(#Platform.org.domain)",
|
||||
ArgoCD + ".\(#ClusterName).\(#Platform.org.domain)",
|
||||
]
|
||||
spec: gateways: ["istio-ingress/default"]
|
||||
spec: http: [{route: [{destination: {
|
||||
host: "argocd-server.\(Namespace).svc.cluster.local"
|
||||
port: number: 80
|
||||
}}]}]
|
||||
}
|
||||
}
|
||||
}
|
||||
let IstioInject = [{op: "add", path: "/spec/template/metadata/labels/sidecar.istio.io~1inject", value: "true"}]
|
||||
|
||||
#Kustomize: _patches: {
|
||||
mesh: {
|
||||
target: {
|
||||
group: "apps"
|
||||
version: "v1"
|
||||
kind: "Deployment"
|
||||
name: "argocd-server"
|
||||
}
|
||||
patch: yaml.Marshal(IstioInject)
|
||||
}
|
||||
}
|
||||
|
||||
// Probably shouldn't use the authproxy struct and should instead define an identity provider struct.
|
||||
let AuthProxySpec = #AuthProxySpec & #Platform.authproxy
|
||||
|
||||
let OIDCConfig = {
|
||||
name: "Holos Platform"
|
||||
issuer: AuthProxySpec.issuer
|
||||
clientID: #Platform.argocd.clientID
|
||||
requestedIDTokenClaims: groups: essential: true
|
||||
requestedScopes: ["openid", "profile", "email", "groups", "urn:zitadel:iam:org:domain:primary:\(AuthProxySpec.orgDomain)"]
|
||||
enablePKCEAuthentication: true
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,48 +7,53 @@ let Name = "gateway"
|
||||
#InputKeys: component: Name
|
||||
#TargetNamespace: "istio-ingress"
|
||||
|
||||
let LoginCert = #PlatformCerts.login
|
||||
|
||||
spec: components: KubernetesObjectsList: [
|
||||
#KubernetesObjects & {
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
_dependsOn: "prod-mesh-istio-base": _
|
||||
_dependsOn: "prod-mesh-ingress": _
|
||||
_dependsOn: "prod-secrets-stores": _
|
||||
_dependsOn: "prod-mesh-istio-base": _
|
||||
_dependsOn: "prod-mesh-ingress": _
|
||||
|
||||
metadata: name: "\(#InstancePrefix)-\(Name)"
|
||||
apiObjectMap: OBJECTS.apiObjectMap
|
||||
},
|
||||
]
|
||||
|
||||
// GatewayServers represents all hosts for all VirtualServices in the cluster attached to Gateway/default
|
||||
// NOTE: This is a critical structure because the default Gateway should be used in most cases.
|
||||
let GatewayServers = {
|
||||
for Project in _Projects {
|
||||
for server in (#ProjectTemplate & {project: Project}).ClusterGatewayServers {
|
||||
(server.port.name): server
|
||||
}
|
||||
}
|
||||
|
||||
for k, svc in #OptionalServices {
|
||||
if svc.enabled && list.Contains(svc.clusterNames, #ClusterName) {
|
||||
for server in svc.servers {
|
||||
(server.port.name): server
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if #PlatformServers[#ClusterName] != _|_ {
|
||||
for server in #PlatformServers[#ClusterName] {
|
||||
(server.port.name): server
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let OBJECTS = #APIObjects & {
|
||||
apiObjects: {
|
||||
ExternalSecret: login: #ExternalSecret & {
|
||||
_name: "login"
|
||||
}
|
||||
Gateway: default: #Gateway & {
|
||||
metadata: name: "default"
|
||||
metadata: namespace: #TargetNamespace
|
||||
|
||||
spec: selector: istio: "ingressgateway"
|
||||
spec: servers: [
|
||||
{
|
||||
hosts: [for dnsName in LoginCert.spec.dnsNames {"prod-iam-zitadel/\(dnsName)"}]
|
||||
port: name: "https-prod-iam-login"
|
||||
port: number: 443
|
||||
port: protocol: "HTTPS"
|
||||
tls: credentialName: LoginCert.spec.secretName
|
||||
tls: mode: "SIMPLE"
|
||||
},
|
||||
]
|
||||
spec: servers: [for x in GatewayServers {x}]
|
||||
}
|
||||
|
||||
for k, svc in #OptionalServices {
|
||||
if svc.enabled && list.Contains(svc.clusterNames, #ClusterName) {
|
||||
Gateway: "\(svc.name)": #Gateway & {
|
||||
metadata: name: svc.name
|
||||
metadata: namespace: #TargetNamespace
|
||||
spec: selector: istio: "ingressgateway"
|
||||
spec: servers: [for s in svc.servers {s}]
|
||||
}
|
||||
for k, s in svc.servers {
|
||||
ExternalSecret: "\(s.tls.credentialName)": _
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package holos
|
||||
let Name = "httpbin"
|
||||
let ComponentName = "\(#InstancePrefix)-\(Name)"
|
||||
|
||||
let SecretName = #InputKeys.cluster + "-" + Name
|
||||
let MatchLabels = {
|
||||
app: Name
|
||||
"app.kubernetes.io/instance": ComponentName
|
||||
@@ -18,7 +17,7 @@ let Metadata = {
|
||||
|
||||
#TargetNamespace: "istio-ingress"
|
||||
|
||||
let Cert = #PlatformCerts[SecretName]
|
||||
let Cert = #PlatformCerts["\(#ClusterName)-httpbin"]
|
||||
|
||||
spec: components: KubernetesObjectsList: [
|
||||
#KubernetesObjects & {
|
||||
@@ -63,24 +62,10 @@ let OBJECTS = #APIObjects & {
|
||||
{port: 80, targetPort: 8080, protocol: "TCP", name: "http"},
|
||||
]
|
||||
}
|
||||
Gateway: httpbin: #Gateway & {
|
||||
metadata: Metadata
|
||||
spec: selector: istio: "ingressgateway"
|
||||
spec: servers: [
|
||||
{
|
||||
hosts: [for host in Cert.spec.dnsNames {"\(#TargetNamespace)/\(host)"}]
|
||||
port: name: "https-\(ComponentName)"
|
||||
port: number: 443
|
||||
port: protocol: "HTTPS"
|
||||
tls: credentialName: Cert.spec.secretName
|
||||
tls: mode: "SIMPLE"
|
||||
},
|
||||
]
|
||||
}
|
||||
VirtualService: httpbin: #VirtualService & {
|
||||
metadata: Metadata
|
||||
spec: hosts: [for host in Cert.spec.dnsNames {host}]
|
||||
spec: gateways: ["\(#TargetNamespace)/\(Name)"]
|
||||
spec: gateways: ["istio-ingress/default"]
|
||||
spec: http: [{route: [{destination: host: Name}]}]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,8 @@ spec: components: HelmChartList: [
|
||||
apiObjectMap: _IngressAuthProxy.Deployment.apiObjectMap
|
||||
// Auth Policy
|
||||
apiObjectMap: _IngressAuthProxy.Policy.apiObjectMap
|
||||
// Auth Policy Exclusions
|
||||
apiObjectMap: _AuthPolicyRules.objects.apiObjectMap
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package holos
|
||||
|
||||
// Ingress Gateway default auth proxy
|
||||
let Provider = _IngressAuthProxy.authproxy.provider
|
||||
let Provider = _IngressAuthProxy.AuthProxySpec.provider
|
||||
let Service = _IngressAuthProxy.service
|
||||
#MeshConfig: extensionProviderMap: (Provider): envoyExtAuthzHttp: service: Service
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@ import "encoding/yaml"
|
||||
// The ingress gateway auth proxy is used by multiple cue instances.
|
||||
// AUTHPROXY configures one oauth2-proxy deployment for each host in each stage of a project. Multiple deployments per stage are used to narrow down the cookie domain.
|
||||
_IngressAuthProxy: {
|
||||
Name: "authproxy"
|
||||
Namespace: "istio-ingress"
|
||||
service: "\(Name).\(Namespace).svc.cluster.local"
|
||||
authproxy: #IngressAuthProxySpec | *#Platform.authproxy
|
||||
Name: "authproxy"
|
||||
Namespace: "istio-ingress"
|
||||
service: "\(Name).\(Namespace).svc.cluster.local"
|
||||
AuthProxySpec: #AuthProxySpec & #Platform.authproxy
|
||||
|
||||
Domains: [DOMAIN=string]: {name: DOMAIN}
|
||||
Domains: (#Platform.org.domain): _
|
||||
@@ -48,8 +48,8 @@ _IngressAuthProxy: {
|
||||
id: "Holos Platform"
|
||||
name: "Holos Platform"
|
||||
provider: "oidc"
|
||||
scope: "openid profile email groups offline_access urn:zitadel:iam:org:domain:primary:\(authproxy.orgDomain)"
|
||||
clientID: authproxy.clientID
|
||||
scope: "openid profile email groups offline_access urn:zitadel:iam:org:domain:primary:\(AuthProxySpec.orgDomain)"
|
||||
clientID: AuthProxySpec.clientID
|
||||
clientSecretFile: "/dev/null"
|
||||
code_challenge_method: "S256"
|
||||
loginURLParameters: [{
|
||||
@@ -57,7 +57,7 @@ _IngressAuthProxy: {
|
||||
name: "approval_prompt"
|
||||
}]
|
||||
oidcConfig: {
|
||||
issuerURL: authproxy.issuer
|
||||
issuerURL: AuthProxySpec.issuer
|
||||
audienceClaims: ["aud"]
|
||||
emailClaim: "email"
|
||||
groupsClaim: "groups"
|
||||
@@ -95,7 +95,7 @@ _IngressAuthProxy: {
|
||||
}]
|
||||
args: [
|
||||
// callback url is proxy prefix + /callback
|
||||
"--proxy-prefix=" + authproxy.proxyPrefix,
|
||||
"--proxy-prefix=" + AuthProxySpec.proxyPrefix,
|
||||
"--email-domain=*",
|
||||
"--session-store-type=redis",
|
||||
"--redis-connection-url=redis://\(RedisMetadata.name):6379",
|
||||
@@ -155,7 +155,7 @@ _IngressAuthProxy: {
|
||||
spec: hosts: ["*"]
|
||||
spec: gateways: ["istio-ingress/default"]
|
||||
spec: http: [{
|
||||
match: [{uri: prefix: authproxy.proxyPrefix}]
|
||||
match: [{uri: prefix: AuthProxySpec.proxyPrefix}]
|
||||
route: [{
|
||||
destination: host: Name
|
||||
destination: port: number: 4180
|
||||
@@ -254,24 +254,70 @@ _IngressAuthProxy: {
|
||||
RequestAuthentication: (Name): #RequestAuthentication & {
|
||||
metadata: Metadata & {name: Name}
|
||||
spec: jwtRules: [{
|
||||
audiences: ["\(authproxy.projectID)"]
|
||||
audiences: ["\(AuthProxySpec.projectID)"]
|
||||
forwardOriginalToken: true
|
||||
fromHeaders: [{name: authproxy.idTokenHeader}]
|
||||
issuer: authproxy.issuer
|
||||
fromHeaders: [{name: AuthProxySpec.idTokenHeader}]
|
||||
issuer: AuthProxySpec.issuer
|
||||
}]
|
||||
spec: selector: matchLabels: istio: "ingressgateway"
|
||||
}
|
||||
AuthorizationPolicy: "\(Name)-custom": {
|
||||
_description: "Route all requests through the auth proxy by default"
|
||||
|
||||
metadata: Metadata & {name: "\(Name)-custom"}
|
||||
spec: {
|
||||
action: "CUSTOM"
|
||||
provider: name: authproxy.provider
|
||||
// bypass the external authorizer when the id token is already in the request.
|
||||
// the RequestAuthentication rule will verify the token.
|
||||
rules: [{when: [{key: "request.headers[\(authproxy.idTokenHeader)]", notValues: ["*"]}]}]
|
||||
provider: name: AuthProxySpec.provider
|
||||
rules: [
|
||||
{
|
||||
to: [{
|
||||
operation: notHosts: [
|
||||
// Never send requests for the login service through the authorizer, would block login.
|
||||
AuthProxySpec.issuerHost,
|
||||
// Exclude hosts with specialized rules from the catch-all.
|
||||
for x in _AuthPolicyRules.hosts {x.name},
|
||||
]
|
||||
}]
|
||||
when: [
|
||||
{
|
||||
// bypass the external authorizer when the id token is already in the request.
|
||||
// the RequestAuthentication rule will verify the token.
|
||||
key: "request.headers[\(AuthProxySpec.idTokenHeader)]"
|
||||
notValues: ["*"]
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
selector: matchLabels: istio: "ingressgateway"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_AuthPolicyRules: #AuthPolicyRules & {
|
||||
hosts: {
|
||||
let Vault = "vault.core.ois.run"
|
||||
(Vault): {
|
||||
slug: "vault"
|
||||
// Rules for when to route requests through the auth proxy
|
||||
spec: rules: [
|
||||
{
|
||||
to: [{
|
||||
operation: hosts: [Vault]
|
||||
operation: paths: ["/ui", "/ui/*"]
|
||||
}]
|
||||
},
|
||||
{
|
||||
to: [{
|
||||
operation: hosts: [Vault]
|
||||
}]
|
||||
when: [{
|
||||
key: "request.headers[x-vault-request]"
|
||||
notValues: ["true"]
|
||||
}]
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ package holos
|
||||
|
||||
spec: components: HelmChartList: [
|
||||
#HelmChart & {
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
_dependsOn: "prod-secrets-stores": _
|
||||
|
||||
metadata: name: "prod-metal-ceph"
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ let OBJECTS = #APIObjects & {
|
||||
metadata: name: Name
|
||||
metadata: namespace: #TargetNamespace
|
||||
spec: hosts: [for cert in Vault.certs {cert.spec.commonName}]
|
||||
spec: gateways: ["istio-ingress/\(Name)"]
|
||||
spec: gateways: ["istio-ingress/default"]
|
||||
spec: http: [
|
||||
{
|
||||
route: [
|
||||
|
||||
@@ -2,14 +2,17 @@ package holos
|
||||
|
||||
#Project: authProxyOrgDomain: "openinfrastructure.co"
|
||||
|
||||
let ZitadelProjectID = 257713952794870157
|
||||
|
||||
_Projects: #Projects & {
|
||||
// The platform project is required and where platform services reside. ArgoCD, Grafana, Prometheus, etc...
|
||||
platform: {
|
||||
resourceId: 257713952794870157
|
||||
clusters: k1: _
|
||||
resourceId: ZitadelProjectID
|
||||
// platform level services typically run in the core cluster pair.
|
||||
clusters: core1: _
|
||||
clusters: core2: _
|
||||
// for development, probably wouldn't run these services in the workload clusters.
|
||||
clusters: k2: _
|
||||
stages: dev: authProxyClientID: "260887327029658738@holos_platform"
|
||||
stages: prod: authProxyClientID: "260887404288738416@holos_platform"
|
||||
// Services hosted in the platform project
|
||||
hosts: argocd: _
|
||||
hosts: grafana: _
|
||||
@@ -17,11 +20,9 @@ _Projects: #Projects & {
|
||||
}
|
||||
|
||||
holos: {
|
||||
resourceId: 260446255245690199
|
||||
resourceId: ZitadelProjectID
|
||||
clusters: k1: _
|
||||
clusters: k2: _
|
||||
stages: dev: authProxyClientID: "260505543108527218@holos"
|
||||
stages: prod: authProxyClientID: "260506079325128023@holos"
|
||||
environments: {
|
||||
prod: stage: "prod"
|
||||
dev: stage: "dev"
|
||||
@@ -32,13 +33,12 @@ _Projects: #Projects & {
|
||||
}
|
||||
|
||||
iam: {
|
||||
resourceId: 260582480954787159
|
||||
resourceId: ZitadelProjectID
|
||||
hosts: login: _
|
||||
clusters: {
|
||||
core1: _
|
||||
core2: _
|
||||
}
|
||||
stages: dev: authProxyClientID: "260582521186616432@iam"
|
||||
stages: prod: authProxyClientID: "260582633862399090@iam"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ package holos
|
||||
|
||||
// Refer to [Using Cert Manager to Deploy TLS for Postgres on Kubernetes](https://www.crunchydata.com/blog/using-cert-manager-to-deploy-tls-for-postgres-on-kubernetes)
|
||||
|
||||
#TargetNamespace: "prod-iam-zitadel"
|
||||
#TargetNamespace: _Projects.iam.environments.prod.namespace
|
||||
#InputKeys: component: "postgres-certs"
|
||||
|
||||
let DBName = "zitadel"
|
||||
|
||||
@@ -22,7 +22,6 @@ let Privileged = {
|
||||
{name: "istio-ingress"} & Restricted,
|
||||
{name: "cert-manager"},
|
||||
{name: "argocd"},
|
||||
{name: "prod-iam-zitadel"},
|
||||
{name: "arc-system"},
|
||||
{name: "arc-runner"},
|
||||
// https://github.com/CrunchyData/postgres-operator-examples/blob/main/kustomize/install/namespace/namespace.yaml
|
||||
|
||||
@@ -55,6 +55,18 @@ import "encoding/yaml"
|
||||
}
|
||||
}
|
||||
|
||||
// ClusterGatewayServers provides a struct of Gateway servers for the current cluster.
|
||||
// This is intended for Gateway/default to add all servers to the default gateway.
|
||||
ClusterGatewayServers: {
|
||||
if project.clusters[#ClusterName] != _|_ {
|
||||
for Stage in project.stages {
|
||||
for server in GatewayServers[Stage.name] {
|
||||
(server.port.name): server
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
workload: resources: {
|
||||
// Provide resources only if the project is managed on --cluster-name
|
||||
if project.clusters[#ClusterName] != _|_ {
|
||||
@@ -64,10 +76,6 @@ import "encoding/yaml"
|
||||
// Istio Gateway
|
||||
"\(stage.slug)-gateway": #KubernetesObjects & {
|
||||
apiObjectMap: (#APIObjects & {
|
||||
apiObjects: Gateway: (stage.slug): #Gateway & {
|
||||
spec: servers: [for server in GatewayServers[stage.name] {server}]
|
||||
}
|
||||
|
||||
for host in GatewayServers[stage.name] {
|
||||
apiObjects: ExternalSecret: (host.tls.credentialName): metadata: namespace: "istio-ingress"
|
||||
}
|
||||
@@ -75,24 +83,32 @@ import "encoding/yaml"
|
||||
}
|
||||
|
||||
// Manage auth-proxy in each stage
|
||||
"\(stage.slug)-authproxy": #KubernetesObjects & {
|
||||
apiObjectMap: (#APIObjects & {
|
||||
apiObjects: (AUTHPROXY & {stage: Stage, project: Project, servers: GatewayServers[stage.name]}).apiObjects
|
||||
}).apiObjectMap
|
||||
if project.features.authproxy.enabled {
|
||||
"\(stage.slug)-authproxy": #KubernetesObjects & {
|
||||
apiObjectMap: (#APIObjects & {
|
||||
apiObjects: (AUTHPROXY & {stage: Stage, project: Project, servers: GatewayServers[stage.name]}).apiObjects
|
||||
}).apiObjectMap
|
||||
}
|
||||
|
||||
for Env in project.environments if Env.stage == stage.name {
|
||||
"\(Env.slug)-authpolicy": #KubernetesObjects & {
|
||||
// Manage auth policy in each env
|
||||
apiObjectMap: (#APIObjects & {
|
||||
apiObjects: (AUTHPOLICY & {env: Env, project: Project, servers: GatewayServers[stage.name]}).apiObjects
|
||||
}).apiObjectMap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Manage httpbin in each environment
|
||||
for Env in project.environments if Env.stage == stage.name {
|
||||
"\(Env.slug)-httpbin": #KubernetesObjects & {
|
||||
let Project = project
|
||||
apiObjectMap: (#APIObjects & {
|
||||
apiObjects: (HTTPBIN & {env: Env, project: Project}).apiObjects
|
||||
}).apiObjectMap
|
||||
|
||||
// Manage auth policy in each env
|
||||
apiObjectMap: (#APIObjects & {
|
||||
apiObjects: (AUTHPOLICY & {env: Env, project: Project, servers: GatewayServers[stage.name]}).apiObjects
|
||||
}).apiObjectMap
|
||||
if project.features.httpbin.enabled {
|
||||
for Env in project.environments if Env.stage == stage.name {
|
||||
"\(Env.slug)-httpbin": #KubernetesObjects & {
|
||||
let Project = project
|
||||
apiObjectMap: (#APIObjects & {
|
||||
apiObjects: (HTTPBIN & {env: Env, project: Project}).apiObjects
|
||||
}).apiObjectMap
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -178,7 +194,7 @@ let HTTPBIN = {
|
||||
let Project = project
|
||||
let Env = env
|
||||
spec: hosts: [for host in (#EnvHosts & {project: Project, env: Env}).hosts {host.name}]
|
||||
spec: gateways: ["istio-ingress/\(env.stageSlug)"]
|
||||
spec: gateways: ["istio-ingress/default"]
|
||||
spec: http: [{route: [{destination: host: Name}]}]
|
||||
}
|
||||
}
|
||||
@@ -194,6 +210,14 @@ let AUTHPROXY = {
|
||||
let Project = project
|
||||
let Stage = stage
|
||||
|
||||
let AuthProxySpec = #AuthProxySpec & {
|
||||
namespace: stage.namespace
|
||||
projectID: project.resourceId
|
||||
clientID: stage.authProxyClientID
|
||||
orgDomain: project.authProxyOrgDomain
|
||||
provider: stage.extAuthzProviderName
|
||||
}
|
||||
|
||||
let Metadata = {
|
||||
name: Name
|
||||
namespace: stage.namespace
|
||||
@@ -224,15 +248,15 @@ let AUTHPROXY = {
|
||||
data: "config.yaml": yaml.Marshal(AuthProxyConfig)
|
||||
let AuthProxyConfig = {
|
||||
injectResponseHeaders: [{
|
||||
name: "x-oidc-id-token"
|
||||
name: AuthProxySpec.idTokenHeader
|
||||
values: [{claim: "id_token"}]
|
||||
}]
|
||||
providers: [{
|
||||
id: "Holos Platform"
|
||||
name: "Holos Platform"
|
||||
provider: "oidc"
|
||||
scope: "openid profile email groups offline_access urn:zitadel:iam:org:domain:primary:\(project.authProxyOrgDomain)"
|
||||
clientID: stage.authProxyClientID
|
||||
scope: "openid profile email groups offline_access urn:zitadel:iam:org:domain:primary:\(AuthProxySpec.orgDomain)"
|
||||
clientID: AuthProxySpec.clientID
|
||||
clientSecretFile: "/dev/null"
|
||||
code_challenge_method: "S256"
|
||||
loginURLParameters: [{
|
||||
@@ -240,7 +264,7 @@ let AUTHPROXY = {
|
||||
name: "approval_prompt"
|
||||
}]
|
||||
oidcConfig: {
|
||||
issuerURL: project.authProxyIssuer
|
||||
issuerURL: AuthProxySpec.issuer
|
||||
audienceClaims: ["aud"]
|
||||
emailClaim: "email"
|
||||
groupsClaim: "groups"
|
||||
@@ -285,7 +309,7 @@ let AUTHPROXY = {
|
||||
}]
|
||||
args: [
|
||||
// callback url is proxy prefix + /callback
|
||||
"--proxy-prefix=" + project.authProxyPrefix,
|
||||
"--proxy-prefix=" + AuthProxySpec.proxyPrefix,
|
||||
"--email-domain=*",
|
||||
"--session-store-type=redis",
|
||||
"--redis-connection-url=redis://\(RedisMetadata.name):6379",
|
||||
@@ -343,9 +367,9 @@ let AUTHPROXY = {
|
||||
VirtualService: (Name): #VirtualService & {
|
||||
metadata: Metadata
|
||||
spec: hosts: ["*"]
|
||||
spec: gateways: ["istio-ingress/\(stage.slug)"]
|
||||
spec: gateways: ["istio-ingress/default"]
|
||||
spec: http: [{
|
||||
match: [{uri: prefix: project.authProxyPrefix}]
|
||||
match: [{uri: prefix: AuthProxySpec.proxyPrefix}]
|
||||
route: [{
|
||||
destination: host: Name
|
||||
destination: port: number: 4180
|
||||
@@ -447,6 +471,14 @@ let AUTHPOLICY = {
|
||||
let stage = project.stages[env.stage]
|
||||
let Env = env
|
||||
|
||||
let AuthProxySpec = #AuthProxySpec & {
|
||||
namespace: stage.namespace
|
||||
projectID: project.resourceId
|
||||
clientID: stage.authProxyClientID
|
||||
orgDomain: project.authProxyOrgDomain
|
||||
provider: stage.extAuthzProviderName
|
||||
}
|
||||
|
||||
let Metadata = {
|
||||
name: string
|
||||
namespace: env.namespace
|
||||
@@ -469,16 +501,16 @@ let AUTHPOLICY = {
|
||||
for host in Hosts {host.name},
|
||||
for host in Hosts {host.name + ":*"},
|
||||
]
|
||||
let MatchLabels = {"security.holos.run/authproxy": stage.extAuthzProviderName}
|
||||
let MatchLabels = {"security.holos.run/authproxy": AuthProxySpec.provider}
|
||||
|
||||
apiObjects: {
|
||||
RequestAuthentication: (Name): #RequestAuthentication & {
|
||||
metadata: Metadata & {name: Name}
|
||||
spec: jwtRules: [{
|
||||
audiences: [stage.authProxyClientID]
|
||||
audiences: [AuthProxySpec.clientID]
|
||||
forwardOriginalToken: true
|
||||
fromHeaders: [{name: "x-oidc-id-token"}]
|
||||
issuer: project.authProxyIssuer
|
||||
fromHeaders: [{name: AuthProxySpec.idTokenHeader}]
|
||||
issuer: AuthProxySpec.issuer
|
||||
}]
|
||||
spec: selector: matchLabels: MatchLabels
|
||||
}
|
||||
@@ -487,8 +519,19 @@ let AUTHPOLICY = {
|
||||
spec: {
|
||||
action: "CUSTOM"
|
||||
// send the request to the auth proxy
|
||||
provider: name: stage.extAuthzProviderName
|
||||
rules: [{to: [{operation: hosts: HostList}]}]
|
||||
provider: name: AuthProxySpec.provider
|
||||
rules: [{
|
||||
to: [{operation: hosts: HostList}]
|
||||
when: [
|
||||
{
|
||||
key: "request.headers[\(AuthProxySpec.idTokenHeader)]"
|
||||
notValues: ["*"]
|
||||
},
|
||||
{
|
||||
key: "request.headers[host]"
|
||||
notValues: [AuthProxySpec.issuerHost]
|
||||
},
|
||||
]}]
|
||||
selector: matchLabels: MatchLabels
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,6 @@ import "strings"
|
||||
}
|
||||
domain: string | *#Platform.org.domain
|
||||
|
||||
// authProxyPrefix is the path routed to the ext auth proxy.
|
||||
authProxyPrefix: string | *"/holos/oidc"
|
||||
// authProxyOrgDomain is the primary org domain for zitadel.
|
||||
authProxyOrgDomain: string | *#Platform.org.domain
|
||||
// authProxyIssuer is the issuer url
|
||||
@@ -63,6 +61,8 @@ import "strings"
|
||||
|
||||
// features is YAGNI maybe?
|
||||
features: [Name=string]: #Feature & {name: Name}
|
||||
features: authproxy: _
|
||||
features: httpbin: _
|
||||
}
|
||||
|
||||
// #Cluster defines a cluster
|
||||
@@ -127,7 +127,7 @@ import "strings"
|
||||
#Feature: {
|
||||
name: string
|
||||
description: string
|
||||
enabled: *true | false
|
||||
enabled: true | *false
|
||||
}
|
||||
|
||||
#ProjectTemplate: {
|
||||
|
||||
@@ -226,24 +226,31 @@ _apiVersion: "holos.run/v1alpha1"
|
||||
name: string & ID
|
||||
}
|
||||
// authproxy configures the auth proxy attached to the default ingress gateway in the istio-ingress namespace.
|
||||
authproxy: #IngressAuthProxySpec
|
||||
authproxy: #AuthProxySpec & {
|
||||
namespace: "istio-ingress"
|
||||
provider: "ingressauth"
|
||||
}
|
||||
}
|
||||
|
||||
#IngressAuthProxySpec: {
|
||||
#AuthProxySpec: {
|
||||
// projectID is the zitadel project resource id.
|
||||
projectID: number
|
||||
// clientID is the zitadel application client id.
|
||||
clientID: string
|
||||
// namespace is the namespace
|
||||
namespace: string
|
||||
// provider is the istio extension provider name in the mesh config.
|
||||
provider: string
|
||||
// orgDomain is the zitadel organization domain for logins.
|
||||
orgDomain: string | *#Platform.org.domain
|
||||
// issuerHost is the Host: header value of the oidc issuer
|
||||
issuerHost: string | *"login.\(#Platform.org.domain)"
|
||||
// issuer is the oidc identity provider issuer url
|
||||
issuer: string | *"https://login.\(#Platform.org.domain)"
|
||||
issuer: string | *"https://\(issuerHost)"
|
||||
// path is the oauth2-proxy --proxy-prefix value. The default callback url is the Host: value with a path of /holos/oidc/callback
|
||||
proxyPrefix: string | *"/holos/oidc"
|
||||
// provider is the istio extension provider name in the mesh config.
|
||||
provider: "ingressauth"
|
||||
proxyPrefix: string | *"/holos/authproxy/\(namespace)"
|
||||
// idTokenHeader represents the header where the id token is placed
|
||||
idTokenHeader: "x-oidc-id-token"
|
||||
idTokenHeader: string | *"x-oidc-id-token"
|
||||
}
|
||||
|
||||
// ManagedNamespace is a namespace to manage across all clusters in the holos platform.
|
||||
|
||||
@@ -1 +1 @@
|
||||
61
|
||||
62
|
||||
|
||||
@@ -1 +1 @@
|
||||
6
|
||||
1
|
||||
|
||||
Reference in New Issue
Block a user