mirror of
https://github.com/holos-run/holos.git
synced 2026-03-19 16:54:58 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d0dae8742 | ||
|
|
61b4b5bd17 | ||
|
|
0060740b76 | ||
|
|
bf8a4af579 | ||
|
|
dc057fe39d | ||
|
|
9877ab131a | ||
|
|
13aba64cb7 | ||
|
|
fe9bc2dbfc | ||
|
|
c53b682852 | ||
|
|
3aca6a9e4c | ||
|
|
40fdfc0317 | ||
|
|
25d9415b0a | ||
|
|
43c8702398 | ||
|
|
ce94776dbb | ||
|
|
78ab6cd848 |
@@ -18,7 +18,10 @@ import "encoding/yaml"
|
||||
Issuer?: [Name=_]: #Issuer & {metadata: name: Name}
|
||||
Gateway?: [Name=_]: #Gateway & {metadata: name: Name}
|
||||
ConfigMap?: [Name=_]: #ConfigMap & {metadata: name: Name}
|
||||
Deployment?: [_]: #Deployment
|
||||
|
||||
Deployment?: [_]: #Deployment
|
||||
RequestAuthentication?: [_]: #RequestAuthentication
|
||||
AuthorizationPolicy?: [_]: #AuthorizationPolicy
|
||||
}
|
||||
|
||||
// apiObjectMap holds the marshalled representation of apiObjects
|
||||
|
||||
54
docs/examples/meshconfig.cue
Normal file
54
docs/examples/meshconfig.cue
Normal file
@@ -0,0 +1,54 @@
|
||||
package holos
|
||||
|
||||
// #MeshConfig provides the istio meshconfig in the config key given projects.
|
||||
#MeshConfig: {
|
||||
projects: #Projects
|
||||
// clusterName is the value of the --cluster-name flag, the cluster currently being manged / rendered.
|
||||
clusterName: string | *#ClusterName
|
||||
|
||||
// for extAuthzHttp extension providers
|
||||
extensionProviderMap: [Name=_]: #ExtAuthzProxy & {name: Name}
|
||||
// for other extension providers like zipkin
|
||||
extensionProviderExtraMap: [Name=_]: {name: Name}
|
||||
|
||||
config: {
|
||||
accessLogEncoding: string | *"JSON"
|
||||
accessLogFile: string | *"/dev/stdout"
|
||||
defaultConfig: {
|
||||
discoveryAddress: string | *"istiod.istio-system.svc:15012"
|
||||
tracing: zipkin: address: string | *"zipkin.istio-system:9411"
|
||||
}
|
||||
defaultProviders: metrics: [...string] | *["prometheus"]
|
||||
enablePrometheusMerge: false | *true
|
||||
rootNamespace: string | *"istio-system"
|
||||
trustDomain: string | *"cluster.local"
|
||||
extensionProviders: [
|
||||
for x in extensionProviderMap {x},
|
||||
for y in extensionProviderExtraMap {y},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// #ExtAuthzProxy defines the provider configuration for an istio external authorization auth proxy.
|
||||
#ExtAuthzProxy: {
|
||||
name: string
|
||||
envoyExtAuthzHttp: {
|
||||
headersToDownstreamOnDeny: [
|
||||
"content-type",
|
||||
"set-cookie",
|
||||
]
|
||||
headersToUpstreamOnAllow: [
|
||||
"authorization",
|
||||
"path",
|
||||
"x-oidc-id-token",
|
||||
]
|
||||
includeAdditionalHeadersInCheck: "X-Auth-Request-Redirect": "%REQ(x-forwarded-proto)%://%REQ(:authority)%%REQ(:path)%%REQ(:query)%"
|
||||
includeRequestHeadersInCheck: [
|
||||
"authorization",
|
||||
"cookie",
|
||||
"x-forwarded-for",
|
||||
]
|
||||
port: 4180
|
||||
service: string
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ spec: components: HelmChartList: [
|
||||
namespace: "istio-system"
|
||||
chart: {
|
||||
name: "base"
|
||||
version: "1.20.3"
|
||||
version: #IstioVersion
|
||||
repository: {
|
||||
name: "istio"
|
||||
url: "https://istio-release.storage.googleapis.com/charts"
|
||||
|
||||
@@ -52,6 +52,10 @@ spec: components: HelmChartList: [
|
||||
}
|
||||
}
|
||||
apiObjectMap: OBJECTS.apiObjectMap
|
||||
// Auth Proxy
|
||||
apiObjectMap: _IngressAuthProxy.Deployment.apiObjectMap
|
||||
// Auth Policy
|
||||
apiObjectMap: _IngressAuthProxy.Policy.apiObjectMap
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ package holos
|
||||
|
||||
#HelmChart: {
|
||||
chart: {
|
||||
version: "1.20.3"
|
||||
version: #IstioVersion
|
||||
repository: {
|
||||
name: "istio"
|
||||
url: "https://istio-release.storage.googleapis.com/charts"
|
||||
|
||||
@@ -1,74 +1,9 @@
|
||||
package holos
|
||||
|
||||
// Ingress Gateway default auth proxy
|
||||
let Provider = _IngressAuthProxy.AuthProxySpec.provider
|
||||
let Service = _IngressAuthProxy.service
|
||||
#MeshConfig: extensionProviderMap: (Provider): envoyExtAuthzHttp: service: Service
|
||||
|
||||
// Istio meshconfig
|
||||
// TODO: Generate per-project extauthz providers.
|
||||
_MeshConfig: {
|
||||
accessLogEncoding: "JSON"
|
||||
accessLogFile: "/dev/stdout"
|
||||
defaultConfig: {
|
||||
discoveryAddress: "istiod.istio-system.svc:15012"
|
||||
tracing: zipkin: address: "zipkin.istio-system:9411"
|
||||
}
|
||||
defaultProviders: metrics: ["prometheus"]
|
||||
enablePrometheusMerge: true
|
||||
// For PROXY PROTOCOL at the ingress gateway.
|
||||
gatewayTopology: {
|
||||
numTrustedProxies: 2
|
||||
}
|
||||
rootNamespace: "istio-system"
|
||||
trustDomain: "cluster.local"
|
||||
extensionProviders: [{
|
||||
name: "cluster-trace"
|
||||
zipkin: {
|
||||
maxTagLength: 56
|
||||
port: 9411
|
||||
service: "zipkin.istio-system.svc"
|
||||
}
|
||||
}, {
|
||||
name: "cluster-gatekeeper"
|
||||
envoyExtAuthzHttp: {
|
||||
headersToDownstreamOnDeny: [
|
||||
"content-type",
|
||||
"set-cookie",
|
||||
]
|
||||
headersToUpstreamOnAllow: [
|
||||
"authorization",
|
||||
"path",
|
||||
"x-auth-request-user",
|
||||
"x-auth-request-email",
|
||||
"x-auth-request-access-token",
|
||||
]
|
||||
includeAdditionalHeadersInCheck: "X-Auth-Request-Redirect": "%REQ(x-forwarded-proto)%://%REQ(:authority)%%REQ(:path)%%REQ(:query)%"
|
||||
includeRequestHeadersInCheck: [
|
||||
"authorization",
|
||||
"cookie",
|
||||
"x-forwarded-for",
|
||||
]
|
||||
port: 4180
|
||||
service: "oauth2-proxy.istio-ingress.svc.cluster.local"
|
||||
}
|
||||
}, {
|
||||
name: "core-authorizer"
|
||||
envoyExtAuthzHttp: {
|
||||
headersToDownstreamOnDeny: [
|
||||
"content-type",
|
||||
"set-cookie",
|
||||
]
|
||||
headersToUpstreamOnAllow: [
|
||||
"authorization",
|
||||
"path",
|
||||
"x-auth-request-user",
|
||||
"x-auth-request-email",
|
||||
"x-auth-request-access-token",
|
||||
]
|
||||
includeAdditionalHeadersInCheck: "X-Auth-Request-Redirect": "%REQ(x-forwarded-proto)%://%REQ(:authority)%%REQ(:path)%%REQ(:query)%"
|
||||
includeRequestHeadersInCheck: [
|
||||
"authorization",
|
||||
"cookie",
|
||||
"x-forwarded-for",
|
||||
]
|
||||
port: 4180
|
||||
service: "oauth2-proxy.prod-core-system.svc.cluster.local"
|
||||
}
|
||||
}]
|
||||
}
|
||||
_MeshConfig: (#MeshConfig & {projects: _Projects}).config
|
||||
|
||||
@@ -126,7 +126,7 @@ package holos
|
||||
hub: "docker.io/istio"
|
||||
|
||||
// Default tag for Istio images.
|
||||
tag: "1.20.3"
|
||||
tag: #IstioVersion
|
||||
|
||||
// Variant of the image to use.
|
||||
// Currently supported are: [debug, distroless]
|
||||
|
||||
@@ -1,3 +1,281 @@
|
||||
package holos
|
||||
|
||||
import "encoding/yaml"
|
||||
|
||||
#InstancePrefix: "prod-mesh"
|
||||
|
||||
#IstioVersion: "1.21.0"
|
||||
|
||||
// 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"
|
||||
AuthProxySpec: #AuthProxySpec & #Platform.authproxy
|
||||
|
||||
Domains: [DOMAIN=string]: {name: DOMAIN}
|
||||
Domains: (#Platform.org.domain): _
|
||||
Domains: "\(#ClusterName).\(#Platform.org.domain)": _
|
||||
|
||||
let Metadata = {
|
||||
name: string
|
||||
namespace: Namespace
|
||||
labels: "app.kubernetes.io/name": name
|
||||
labels: "app.kubernetes.io/part-of": "istio-ingressgateway"
|
||||
...
|
||||
}
|
||||
|
||||
let ProxyMetadata = Metadata & {name: Name}
|
||||
let RedisMetadata = Metadata & {name: Name + "-redis"}
|
||||
|
||||
// Deployment represents the oauth2-proxy deployment
|
||||
Deployment: #APIObjects & {
|
||||
apiObjects: {
|
||||
// oauth2-proxy
|
||||
ExternalSecret: (Name): metadata: ProxyMetadata
|
||||
// Place the ID token in a header that does not conflict with the Authorization header.
|
||||
// Refer to: https://github.com/oauth2-proxy/oauth2-proxy/issues/1877#issuecomment-1364033723
|
||||
ConfigMap: (Name): {
|
||||
metadata: ProxyMetadata
|
||||
data: "config.yaml": yaml.Marshal(AuthProxyConfig)
|
||||
let AuthProxyConfig = {
|
||||
injectResponseHeaders: [{
|
||||
name: "x-oidc-id-token"
|
||||
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:\(AuthProxySpec.orgDomain)"
|
||||
clientID: AuthProxySpec.clientID
|
||||
clientSecretFile: "/dev/null"
|
||||
code_challenge_method: "S256"
|
||||
loginURLParameters: [{
|
||||
default: ["force"]
|
||||
name: "approval_prompt"
|
||||
}]
|
||||
oidcConfig: {
|
||||
issuerURL: AuthProxySpec.issuer
|
||||
audienceClaims: ["aud"]
|
||||
emailClaim: "email"
|
||||
groupsClaim: "groups"
|
||||
userIDClaim: "sub"
|
||||
}
|
||||
}]
|
||||
server: BindAddress: ":4180"
|
||||
upstreamConfig: upstreams: [{
|
||||
id: "static://200"
|
||||
path: "/"
|
||||
static: true
|
||||
staticCode: 200
|
||||
}]
|
||||
}
|
||||
}
|
||||
Deployment: (Name): #Deployment & {
|
||||
metadata: ProxyMetadata
|
||||
|
||||
spec: {
|
||||
replicas: 1
|
||||
selector: matchLabels: ProxyMetadata.labels
|
||||
template: {
|
||||
metadata: labels: ProxyMetadata.labels
|
||||
metadata: labels: #IstioSidecar
|
||||
spec: {
|
||||
securityContext: seccompProfile: type: "RuntimeDefault"
|
||||
containers: [{
|
||||
image: "quay.io/oauth2-proxy/oauth2-proxy:v7.6.0"
|
||||
imagePullPolicy: "IfNotPresent"
|
||||
name: "oauth2-proxy"
|
||||
volumeMounts: [{
|
||||
name: "config"
|
||||
mountPath: "/config"
|
||||
readOnly: true
|
||||
}]
|
||||
args: [
|
||||
// callback url is proxy prefix + /callback
|
||||
"--proxy-prefix=" + AuthProxySpec.proxyPrefix,
|
||||
"--email-domain=*",
|
||||
"--session-store-type=redis",
|
||||
"--redis-connection-url=redis://\(RedisMetadata.name):6379",
|
||||
"--cookie-refresh=12h",
|
||||
"--cookie-expire=2160h",
|
||||
"--cookie-secure=true",
|
||||
"--cookie-name=__Secure-\(#ClusterName)-ingress-\(Name)",
|
||||
"--cookie-samesite=lax",
|
||||
for domain in Domains {"--cookie-domain=.\(domain.name)"},
|
||||
for domain in Domains {"--cookie-domain=\(domain.name)"},
|
||||
for domain in Domains {"--whitelist-domain=.\(domain.name)"},
|
||||
for domain in Domains {"--whitelist-domain=\(domain.name)"},
|
||||
"--cookie-csrf-per-request=true",
|
||||
"--cookie-csrf-expire=120s",
|
||||
// will skip authentication for OPTIONS requests
|
||||
"--skip-auth-preflight=true",
|
||||
"--real-client-ip-header=X-Forwarded-For",
|
||||
"--skip-provider-button=true",
|
||||
"--auth-logging",
|
||||
"--alpha-config=/config/config.yaml",
|
||||
]
|
||||
env: [{
|
||||
name: "OAUTH2_PROXY_COOKIE_SECRET"
|
||||
// echo '{"cookiesecret":"'$(LC_ALL=C tr -dc "[:alpha:]" </dev/random | tr '[:upper:]' '[:lower:]' | head -c 32)'"}' | holos create secret -n istio-ingress --append-hash=false --data-stdin authproxy
|
||||
valueFrom: secretKeyRef: {
|
||||
key: "cookiesecret"
|
||||
name: Name
|
||||
}
|
||||
}]
|
||||
ports: [{
|
||||
containerPort: 4180
|
||||
protocol: "TCP"
|
||||
}]
|
||||
securityContext: {
|
||||
seccompProfile: type: "RuntimeDefault"
|
||||
allowPrivilegeEscalation: false
|
||||
runAsNonRoot: true
|
||||
runAsUser: 8192
|
||||
runAsGroup: 8192
|
||||
capabilities: drop: ["ALL"]
|
||||
}
|
||||
}]
|
||||
volumes: [{name: "config", configMap: name: Name}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Service: (Name): #Service & {
|
||||
metadata: ProxyMetadata
|
||||
spec: selector: ProxyMetadata.labels
|
||||
spec: ports: [
|
||||
{port: 4180, targetPort: 4180, protocol: "TCP", name: "http"},
|
||||
]
|
||||
}
|
||||
VirtualService: (Name): #VirtualService & {
|
||||
metadata: ProxyMetadata
|
||||
spec: hosts: ["*"]
|
||||
spec: gateways: ["istio-ingress/default"]
|
||||
spec: http: [{
|
||||
match: [{uri: prefix: AuthProxySpec.proxyPrefix}]
|
||||
route: [{
|
||||
destination: host: Name
|
||||
destination: port: number: 4180
|
||||
}]
|
||||
}]
|
||||
}
|
||||
|
||||
// redis
|
||||
ConfigMap: (RedisMetadata.name): {
|
||||
metadata: RedisMetadata
|
||||
data: "redis.conf": """
|
||||
maxmemory 128mb
|
||||
maxmemory-policy allkeys-lru
|
||||
"""
|
||||
}
|
||||
Deployment: (RedisMetadata.name): {
|
||||
metadata: RedisMetadata
|
||||
spec: {
|
||||
selector: matchLabels: RedisMetadata.labels
|
||||
template: {
|
||||
metadata: labels: RedisMetadata.labels
|
||||
metadata: labels: #IstioSidecar
|
||||
spec: securityContext: seccompProfile: type: "RuntimeDefault"
|
||||
spec: {
|
||||
containers: [{
|
||||
command: [
|
||||
"redis-server",
|
||||
"/redis-master/redis.conf",
|
||||
]
|
||||
env: [{
|
||||
name: "MASTER"
|
||||
value: "true"
|
||||
}]
|
||||
image: "quay.io/holos/redis:7.2.4"
|
||||
livenessProbe: {
|
||||
initialDelaySeconds: 15
|
||||
tcpSocket: port: "redis"
|
||||
}
|
||||
name: "redis"
|
||||
ports: [{
|
||||
containerPort: 6379
|
||||
name: "redis"
|
||||
}]
|
||||
readinessProbe: {
|
||||
exec: command: [
|
||||
"redis-cli",
|
||||
"ping",
|
||||
]
|
||||
initialDelaySeconds: 5
|
||||
}
|
||||
resources: limits: cpu: "0.5"
|
||||
securityContext: {
|
||||
seccompProfile: type: "RuntimeDefault"
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities: drop: ["ALL"]
|
||||
runAsNonRoot: true
|
||||
runAsUser: 999
|
||||
runAsGroup: 999
|
||||
}
|
||||
volumeMounts: [{
|
||||
mountPath: "/redis-master-data"
|
||||
name: "data"
|
||||
}, {
|
||||
mountPath: "/redis-master"
|
||||
name: "config"
|
||||
}]
|
||||
}]
|
||||
volumes: [{
|
||||
emptyDir: {}
|
||||
name: "data"
|
||||
}, {
|
||||
configMap: name: RedisMetadata.name
|
||||
name: "config"
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Service: (RedisMetadata.name): #Service & {
|
||||
metadata: RedisMetadata
|
||||
spec: selector: RedisMetadata.labels
|
||||
spec: type: "ClusterIP"
|
||||
spec: ports: [{
|
||||
name: "redis"
|
||||
port: 6379
|
||||
protocol: "TCP"
|
||||
targetPort: 6379
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Policy represents the AuthorizationPolicy and RequestAuthentication policy
|
||||
Policy: #APIObjects & {
|
||||
apiObjects: {
|
||||
RequestAuthentication: (Name): #RequestAuthentication & {
|
||||
metadata: Metadata & {name: Name}
|
||||
spec: jwtRules: [{
|
||||
audiences: ["\(AuthProxySpec.projectID)"]
|
||||
forwardOriginalToken: true
|
||||
fromHeaders: [{name: AuthProxySpec.idTokenHeader}]
|
||||
issuer: AuthProxySpec.issuer
|
||||
}]
|
||||
spec: selector: matchLabels: istio: "ingressgateway"
|
||||
}
|
||||
AuthorizationPolicy: "\(Name)-custom": {
|
||||
metadata: Metadata & {name: "\(Name)-custom"}
|
||||
spec: {
|
||||
action: "CUSTOM"
|
||||
provider: name: AuthProxySpec.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[\(AuthProxySpec.idTokenHeader)]", notValues: ["*"]},
|
||||
// TODO: Define a way for hosts to be excluded.
|
||||
{key: "request.headers[host]", notValues: [AuthProxySpec.issuerHost]},
|
||||
]}]
|
||||
selector: matchLabels: istio: "ingressgateway"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,20 +172,14 @@ package holos
|
||||
enabled: true
|
||||
// Indicates whether to enable WebAssembly runtime for stats filter.
|
||||
wasmEnabled: false
|
||||
// overrides stats EnvoyFilter configuration.
|
||||
configOverride: {
|
||||
gateway: {}
|
||||
inboundSidecar: {}
|
||||
outboundSidecar: {}
|
||||
}
|
||||
}
|
||||
// stackdriver filter settings.
|
||||
stackdriver: {
|
||||
enabled: false
|
||||
logging: false
|
||||
monitoring: false
|
||||
topology: false // deprecated. setting this to true will have no effect, as this option is no longer supported.
|
||||
disableOutbound: false
|
||||
enabled: false
|
||||
logging: false
|
||||
monitoring: false
|
||||
topology: false // deprecated. setting this to true will have no effect, as this option is no longer supported.
|
||||
|
||||
// configOverride parts give you the ability to override the low level configuration params passed to envoy filter.
|
||||
|
||||
configOverride: {}
|
||||
@@ -248,7 +242,7 @@ package holos
|
||||
// Dev builds from prow are on gcr.io
|
||||
hub: string | *"docker.io/istio"
|
||||
// Default tag for Istio images.
|
||||
tag: string | *"1.20.3"
|
||||
tag: #IstioVersion
|
||||
// Variant of the image to use.
|
||||
// Currently supported are: [debug, distroless]
|
||||
variant: string | *""
|
||||
|
||||
@@ -3,12 +3,23 @@ package holos
|
||||
#Project: authProxyOrgDomain: "openinfrastructure.co"
|
||||
|
||||
_Projects: #Projects & {
|
||||
// The platform project is required and where platform services reside. ArgoCD, Grafana, Prometheus, etc...
|
||||
platform: {
|
||||
resourceId: 257713952794870157
|
||||
clusters: k1: _
|
||||
clusters: k2: _
|
||||
stages: dev: authProxyClientID: "260887327029658738@holos_platform"
|
||||
stages: prod: authProxyClientID: "260887404288738416@holos_platform"
|
||||
// Services hosted in the platform project
|
||||
hosts: argocd: _
|
||||
hosts: grafana: _
|
||||
hosts: prometheus: _
|
||||
}
|
||||
|
||||
holos: {
|
||||
resourceId: 260446255245690199
|
||||
clusters: {
|
||||
k1: _
|
||||
k2: _
|
||||
}
|
||||
clusters: k1: _
|
||||
clusters: k2: _
|
||||
stages: dev: authProxyClientID: "260505543108527218@holos"
|
||||
stages: prod: authProxyClientID: "260506079325128023@holos"
|
||||
environments: {
|
||||
@@ -21,10 +32,13 @@ _Projects: #Projects & {
|
||||
}
|
||||
|
||||
iam: {
|
||||
resourceId: 260582480954787159
|
||||
clusters: {
|
||||
core1: _
|
||||
core2: _
|
||||
}
|
||||
stages: dev: authProxyClientID: "260582521186616432@iam"
|
||||
stages: prod: authProxyClientID: "260582633862399090@iam"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
34
docs/examples/platforms/reference/meshconfig.cue
Normal file
34
docs/examples/platforms/reference/meshconfig.cue
Normal file
@@ -0,0 +1,34 @@
|
||||
package holos
|
||||
|
||||
#MeshConfig: {
|
||||
projects: _
|
||||
clusterName: _
|
||||
|
||||
extensionProviderExtraMap: {
|
||||
"cluster-trace": {
|
||||
zipkin: {
|
||||
maxTagLength: 56
|
||||
port: 9411
|
||||
service: "zipkin.istio-system.svc"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config: {
|
||||
// For PROXY PROTOCOL at the ingress gateway.
|
||||
gatewayTopology: {
|
||||
numTrustedProxies: 2
|
||||
}
|
||||
}
|
||||
|
||||
// Configure an ExtAuthzHttp provider for each stage's authproxy
|
||||
for Project in projects {
|
||||
if Project.clusters[clusterName] != _|_ {
|
||||
for Stage in Project.stages {
|
||||
extensionProviderMap: (Stage.extAuthzProviderName): #ExtAuthzProxy & {
|
||||
envoyExtAuthzHttp: service: "authproxy.\(Stage.namespace).svc.cluster.local"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package holos
|
||||
|
||||
import "encoding/yaml"
|
||||
|
||||
// Platform level definition of a project.
|
||||
#Project: {
|
||||
name: string
|
||||
@@ -73,19 +75,32 @@ package holos
|
||||
}
|
||||
|
||||
// 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 & {
|
||||
apiObjectMap: (#APIObjects & {
|
||||
if project.features.httpbin.enabled {
|
||||
for Env in project.environments if Env.stage == stage.name {
|
||||
"\(Env.slug)-httpbin": #KubernetesObjects & {
|
||||
let Project = project
|
||||
apiObjects: (HTTPBIN & {env: Env, project: Project}).apiObjects
|
||||
}).apiObjectMap
|
||||
apiObjectMap: (#APIObjects & {
|
||||
apiObjects: (HTTPBIN & {env: Env, project: Project}).apiObjects
|
||||
}).apiObjectMap
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,12 +138,19 @@ let HTTPBIN = {
|
||||
project: #Project
|
||||
env: #Environment
|
||||
let Name = name
|
||||
let Stage = project.stages[env.stage]
|
||||
|
||||
let Metadata = {
|
||||
name: Name
|
||||
namespace: env.namespace
|
||||
labels: app: name
|
||||
}
|
||||
let Labels = {
|
||||
"app.kubernetes.io/name": Name
|
||||
"app.kubernetes.io/instance": env.slug
|
||||
"app.kubernetes.io/part-of": env.project
|
||||
"security.holos.run/authproxy": Stage.extAuthzProviderName
|
||||
}
|
||||
|
||||
apiObjects: {
|
||||
Deployment: (Name): #Deployment & {
|
||||
@@ -136,7 +158,7 @@ let HTTPBIN = {
|
||||
|
||||
spec: selector: matchLabels: Metadata.labels
|
||||
spec: template: {
|
||||
metadata: labels: Metadata.labels & #IstioSidecar
|
||||
metadata: labels: Metadata.labels & #IstioSidecar & Labels
|
||||
spec: securityContext: seccompProfile: type: "RuntimeDefault"
|
||||
spec: containers: [{
|
||||
name: Name
|
||||
@@ -180,6 +202,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
|
||||
@@ -203,6 +233,45 @@ let AUTHPROXY = {
|
||||
apiObjects: {
|
||||
// oauth2-proxy
|
||||
ExternalSecret: (Name): metadata: Metadata
|
||||
// Place the ID token in a header that does not conflict with the Authorization header.
|
||||
// Refer to: https://github.com/oauth2-proxy/oauth2-proxy/issues/1877#issuecomment-1364033723
|
||||
ConfigMap: (Name): {
|
||||
metadata: Metadata
|
||||
data: "config.yaml": yaml.Marshal(AuthProxyConfig)
|
||||
let AuthProxyConfig = {
|
||||
injectResponseHeaders: [{
|
||||
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:\(AuthProxySpec.orgDomain)"
|
||||
clientID: AuthProxySpec.clientID
|
||||
clientSecretFile: "/dev/null"
|
||||
code_challenge_method: "S256"
|
||||
loginURLParameters: [{
|
||||
default: ["force"]
|
||||
name: "approval_prompt"
|
||||
}]
|
||||
oidcConfig: {
|
||||
issuerURL: AuthProxySpec.issuer
|
||||
audienceClaims: ["aud"]
|
||||
emailClaim: "email"
|
||||
groupsClaim: "groups"
|
||||
userIDClaim: "sub"
|
||||
}
|
||||
}]
|
||||
server: BindAddress: ":4180"
|
||||
upstreamConfig: upstreams: [{
|
||||
id: "static://200"
|
||||
path: "/"
|
||||
static: true
|
||||
staticCode: 200
|
||||
}]
|
||||
}
|
||||
}
|
||||
Deployment: (Name): #Deployment & {
|
||||
metadata: Metadata
|
||||
|
||||
@@ -219,65 +288,64 @@ let AUTHPROXY = {
|
||||
template: {
|
||||
metadata: labels: Metadata.labels
|
||||
metadata: labels: #IstioSidecar
|
||||
spec: securityContext: seccompProfile: type: "RuntimeDefault"
|
||||
spec: containers: [{
|
||||
image: "quay.io/oauth2-proxy/oauth2-proxy:v7.4.0"
|
||||
imagePullPolicy: "IfNotPresent"
|
||||
name: "oauth2-proxy"
|
||||
args: [
|
||||
// callback url is proxy prefix + /callback
|
||||
"--proxy-prefix=" + project.authProxyPrefix,
|
||||
"--email-domain=*",
|
||||
"--session-store-type=redis",
|
||||
"--redis-connection-url=redis://\(RedisMetadata.name):6379",
|
||||
"--cookie-refresh=12h",
|
||||
"--cookie-expire=2160h",
|
||||
"--cookie-secure=true",
|
||||
"--cookie-name=__Secure-\(Name)-\(stage.slug)",
|
||||
"--cookie-samesite=lax",
|
||||
for domain in StageDomains {"--cookie-domain=.\(domain.name)"},
|
||||
for domain in StageDomains {"--whitelist-domain=.\(domain.name)"},
|
||||
"--cookie-csrf-per-request=true",
|
||||
"--cookie-csrf-expire=120s",
|
||||
"--set-authorization-header=false",
|
||||
"--set-xauthrequest=true",
|
||||
"--pass-access-token=true",
|
||||
"--pass-authorization-header=true",
|
||||
"--upstream=static://200",
|
||||
"--reverse-proxy",
|
||||
"--real-client-ip-header=X-Forwarded-For",
|
||||
"--skip-provider-button=true",
|
||||
"--auth-logging",
|
||||
"--provider=oidc",
|
||||
"--scope=openid profile email groups offline_access urn:zitadel:iam:org:domain:primary:\(project.authProxyOrgDomain)",
|
||||
"--client-id=" + stage.authProxyClientID,
|
||||
"--client-secret-file=/dev/null",
|
||||
"--oidc-issuer-url=\(project.authProxyIssuer)",
|
||||
"--code-challenge-method=S256",
|
||||
"--http-address=0.0.0.0:4180",
|
||||
// "--allowed-group=\(project.resourceId):\(stage.name)-access",
|
||||
]
|
||||
env: [{
|
||||
name: "OAUTH2_PROXY_COOKIE_SECRET"
|
||||
// echo '{"cookiesecret":"'$(LC_ALL=C tr -dc "[:alpha:]" </dev/random | tr '[:upper:]' '[:lower:]' | head -c 32)'"}' | holos create secret -n dev-holos-system --append-hash=false --data-stdin authproxy
|
||||
valueFrom: secretKeyRef: {
|
||||
key: "cookiesecret"
|
||||
name: Name
|
||||
spec: {
|
||||
securityContext: seccompProfile: type: "RuntimeDefault"
|
||||
containers: [{
|
||||
image: "quay.io/oauth2-proxy/oauth2-proxy:v7.6.0"
|
||||
imagePullPolicy: "IfNotPresent"
|
||||
name: "oauth2-proxy"
|
||||
volumeMounts: [{
|
||||
name: "config"
|
||||
mountPath: "/config"
|
||||
readOnly: true
|
||||
}]
|
||||
args: [
|
||||
// callback url is proxy prefix + /callback
|
||||
"--proxy-prefix=" + AuthProxySpec.proxyPrefix,
|
||||
"--email-domain=*",
|
||||
"--session-store-type=redis",
|
||||
"--redis-connection-url=redis://\(RedisMetadata.name):6379",
|
||||
"--cookie-refresh=12h",
|
||||
"--cookie-expire=2160h",
|
||||
"--cookie-secure=true",
|
||||
"--cookie-name=__Secure-\(stage.slug)-\(Name)",
|
||||
"--cookie-samesite=lax",
|
||||
for domain in StageDomains {"--cookie-domain=.\(domain.name)"},
|
||||
for domain in StageDomains {"--cookie-domain=\(domain.name)"},
|
||||
for domain in StageDomains {"--whitelist-domain=.\(domain.name)"},
|
||||
for domain in StageDomains {"--whitelist-domain=\(domain.name)"},
|
||||
"--cookie-csrf-per-request=true",
|
||||
"--cookie-csrf-expire=120s",
|
||||
// will skip authentication for OPTIONS requests
|
||||
"--skip-auth-preflight=true",
|
||||
"--real-client-ip-header=X-Forwarded-For",
|
||||
"--skip-provider-button=true",
|
||||
"--auth-logging",
|
||||
"--alpha-config=/config/config.yaml",
|
||||
]
|
||||
env: [{
|
||||
name: "OAUTH2_PROXY_COOKIE_SECRET"
|
||||
// echo '{"cookiesecret":"'$(LC_ALL=C tr -dc "[:alpha:]" </dev/random | tr '[:upper:]' '[:lower:]' | head -c 32)'"}' | holos create secret -n dev-holos-system --append-hash=false --data-stdin authproxy
|
||||
valueFrom: secretKeyRef: {
|
||||
key: "cookiesecret"
|
||||
name: Name
|
||||
}
|
||||
}]
|
||||
ports: [{
|
||||
containerPort: 4180
|
||||
protocol: "TCP"
|
||||
}]
|
||||
securityContext: {
|
||||
seccompProfile: type: "RuntimeDefault"
|
||||
allowPrivilegeEscalation: false
|
||||
runAsNonRoot: true
|
||||
runAsUser: 8192
|
||||
runAsGroup: 8192
|
||||
capabilities: drop: ["ALL"]
|
||||
}
|
||||
}]
|
||||
ports: [{
|
||||
containerPort: 4180
|
||||
protocol: "TCP"
|
||||
}]
|
||||
securityContext: {
|
||||
seccompProfile: type: "RuntimeDefault"
|
||||
allowPrivilegeEscalation: false
|
||||
runAsNonRoot: true
|
||||
runAsUser: 8192
|
||||
runAsGroup: 8192
|
||||
capabilities: drop: ["ALL"]
|
||||
}
|
||||
}]
|
||||
volumes: [{name: "config", configMap: name: Name}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -285,18 +353,18 @@ let AUTHPROXY = {
|
||||
metadata: Metadata
|
||||
spec: selector: Metadata.labels
|
||||
spec: ports: [
|
||||
{port: 80, targetPort: 4180, protocol: "TCP", name: "http"},
|
||||
{port: 4180, targetPort: 4180, protocol: "TCP", name: "http"},
|
||||
]
|
||||
}
|
||||
VirtualService: (Name): #VirtualService & {
|
||||
metadata: Metadata
|
||||
spec: hosts: [for host, v in servers {host}]
|
||||
spec: hosts: ["*"]
|
||||
spec: gateways: ["istio-ingress/\(stage.slug)"]
|
||||
spec: http: [{
|
||||
match: [{uri: prefix: project.authProxyPrefix}]
|
||||
match: [{uri: prefix: AuthProxySpec.proxyPrefix}]
|
||||
route: [{
|
||||
destination: host: Name
|
||||
destination: port: number: 80
|
||||
destination: port: number: 4180
|
||||
}]
|
||||
}]
|
||||
}
|
||||
@@ -349,6 +417,9 @@ let AUTHPROXY = {
|
||||
seccompProfile: type: "RuntimeDefault"
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities: drop: ["ALL"]
|
||||
runAsNonRoot: true
|
||||
runAsUser: 999
|
||||
runAsGroup: 999
|
||||
}
|
||||
volumeMounts: [{
|
||||
mountPath: "/redis-master-data"
|
||||
@@ -382,3 +453,79 @@ let AUTHPROXY = {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AUTHPOLICY configures the baseline AuthorizationPolicy and RequestAuthentication policy for each stage of each project.
|
||||
let AUTHPOLICY = {
|
||||
project: #Project
|
||||
env: #Environment
|
||||
let Name = "\(stage.slug)-authproxy"
|
||||
let Project = project
|
||||
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
|
||||
labels: {
|
||||
"app.kubernetes.io/name": name
|
||||
"app.kubernetes.io/instance": stage.name
|
||||
"app.kubernetes.io/part-of": stage.project
|
||||
}
|
||||
}
|
||||
|
||||
// Collect all the hosts associated with the stage
|
||||
let Hosts = {
|
||||
for HOST in (#EnvHosts & {project: Project, env: Env}).hosts {
|
||||
(HOST.name): HOST
|
||||
}
|
||||
}
|
||||
|
||||
// HostList is a list of hosts for AuthorizationPolicy rules
|
||||
let HostList = [
|
||||
for host in Hosts {host.name},
|
||||
for host in Hosts {host.name + ":*"},
|
||||
]
|
||||
let MatchLabels = {"security.holos.run/authproxy": AuthProxySpec.provider}
|
||||
|
||||
apiObjects: {
|
||||
RequestAuthentication: (Name): #RequestAuthentication & {
|
||||
metadata: Metadata & {name: Name}
|
||||
spec: jwtRules: [{
|
||||
audiences: [AuthProxySpec.clientID]
|
||||
forwardOriginalToken: true
|
||||
fromHeaders: [{name: AuthProxySpec.idTokenHeader}]
|
||||
issuer: AuthProxySpec.issuer
|
||||
}]
|
||||
spec: selector: matchLabels: MatchLabels
|
||||
}
|
||||
AuthorizationPolicy: "\(Name)-custom": {
|
||||
metadata: Metadata & {name: "\(Name)-custom"}
|
||||
spec: {
|
||||
action: "CUSTOM"
|
||||
// send the request to the auth proxy
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@ import "strings"
|
||||
// #Projects is a map of all the projects in the platform.
|
||||
#Projects: [Name=_]: #Project & {name: Name}
|
||||
|
||||
// The platform project is required and where platform services reside. ArgoCD, Grafana, Prometheus, etc...
|
||||
#Projects: platform: _
|
||||
|
||||
#Project: {
|
||||
name: string
|
||||
// resourceId is the zitadel project Resource ID
|
||||
@@ -23,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
|
||||
@@ -60,6 +61,8 @@ import "strings"
|
||||
|
||||
// features is YAGNI maybe?
|
||||
features: [Name=string]: #Feature & {name: Name}
|
||||
features: authproxy: _
|
||||
features: httpbin: _
|
||||
}
|
||||
|
||||
// #Cluster defines a cluster
|
||||
@@ -117,12 +120,14 @@ import "strings"
|
||||
stageSegments: [...string] | *[name]
|
||||
// authProxyClientID is the ClientID registered with the oidc issuer.
|
||||
authProxyClientID: string
|
||||
// extAuthzProviderName is the provider name in the mesh config
|
||||
extAuthzProviderName: "\(slug)-authproxy"
|
||||
}
|
||||
|
||||
#Feature: {
|
||||
name: string
|
||||
description: string
|
||||
enabled: *true | false
|
||||
enabled: true | *false
|
||||
}
|
||||
|
||||
#ProjectTemplate: {
|
||||
|
||||
@@ -13,6 +13,8 @@ import (
|
||||
crt "cert-manager.io/certificate/v1"
|
||||
gw "networking.istio.io/gateway/v1beta1"
|
||||
vs "networking.istio.io/virtualservice/v1beta1"
|
||||
ra "security.istio.io/requestauthentication/v1"
|
||||
ap "security.istio.io/authorizationpolicy/v1"
|
||||
pg "postgres-operator.crunchydata.com/postgrescluster/v1beta1"
|
||||
)
|
||||
|
||||
@@ -63,19 +65,21 @@ _apiVersion: "holos.run/v1alpha1"
|
||||
#ClusterRoleBinding: #ClusterObject & rbacv1.#ClusterRoleBinding
|
||||
#ClusterIssuer: #ClusterObject & ci.#ClusterIssuer & {...}
|
||||
|
||||
#Issuer: #NamespaceObject & is.#Issuer
|
||||
#Role: #NamespaceObject & rbacv1.#Role
|
||||
#RoleBinding: #NamespaceObject & rbacv1.#RoleBinding
|
||||
#ConfigMap: #NamespaceObject & corev1.#ConfigMap
|
||||
#ServiceAccount: #NamespaceObject & corev1.#ServiceAccount
|
||||
#Pod: #NamespaceObject & corev1.#Pod
|
||||
#Service: #NamespaceObject & corev1.#Service
|
||||
#Job: #NamespaceObject & batchv1.#Job
|
||||
#CronJob: #NamespaceObject & batchv1.#CronJob
|
||||
#Deployment: #NamespaceObject & appsv1.#Deployment
|
||||
#VirtualService: #NamespaceObject & vs.#VirtualService
|
||||
#Certificate: #NamespaceObject & crt.#Certificate
|
||||
#PostgresCluster: #NamespaceObject & pg.#PostgresCluster
|
||||
#Issuer: #NamespaceObject & is.#Issuer
|
||||
#Role: #NamespaceObject & rbacv1.#Role
|
||||
#RoleBinding: #NamespaceObject & rbacv1.#RoleBinding
|
||||
#ConfigMap: #NamespaceObject & corev1.#ConfigMap
|
||||
#ServiceAccount: #NamespaceObject & corev1.#ServiceAccount
|
||||
#Pod: #NamespaceObject & corev1.#Pod
|
||||
#Service: #NamespaceObject & corev1.#Service
|
||||
#Job: #NamespaceObject & batchv1.#Job
|
||||
#CronJob: #NamespaceObject & batchv1.#CronJob
|
||||
#Deployment: #NamespaceObject & appsv1.#Deployment
|
||||
#VirtualService: #NamespaceObject & vs.#VirtualService
|
||||
#RequestAuthentication: #NamespaceObject & ra.#RequestAuthentication
|
||||
#AuthorizationPolicy: #NamespaceObject & ap.#AuthorizationPolicy
|
||||
#Certificate: #NamespaceObject & crt.#Certificate
|
||||
#PostgresCluster: #NamespaceObject & pg.#PostgresCluster
|
||||
|
||||
#Gateway: #NamespaceObject & gw.#Gateway & {
|
||||
metadata: namespace: string | *"istio-ingress"
|
||||
@@ -108,7 +112,7 @@ _apiVersion: "holos.run/v1alpha1"
|
||||
_name: string
|
||||
metadata: {
|
||||
name: _name
|
||||
namespace: #TargetNamespace
|
||||
namespace: string | *#TargetNamespace
|
||||
}
|
||||
spec: {
|
||||
refreshInterval: string | *"1h"
|
||||
@@ -221,6 +225,32 @@ _apiVersion: "holos.run/v1alpha1"
|
||||
services: [ID=_]: {
|
||||
name: string & ID
|
||||
}
|
||||
// authproxy configures the auth proxy attached to the default ingress gateway in the istio-ingress namespace.
|
||||
authproxy: #AuthProxySpec & {
|
||||
namespace: "istio-ingress"
|
||||
provider: "ingressauth"
|
||||
}
|
||||
}
|
||||
|
||||
#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://\(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/authproxy/\(namespace)"
|
||||
// idTokenHeader represents the header where the id token is placed
|
||||
idTokenHeader: string | *"x-oidc-id-token"
|
||||
}
|
||||
|
||||
// ManagedNamespace is a namespace to manage across all clusters in the holos platform.
|
||||
|
||||
@@ -1 +1 @@
|
||||
3
|
||||
7
|
||||
|
||||
Reference in New Issue
Block a user