mirror of
https://github.com/holos-run/holos.git
synced 2026-03-19 16:54:58 +00:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d0dae8742 | ||
|
|
61b4b5bd17 | ||
|
|
0060740b76 | ||
|
|
bf8a4af579 | ||
|
|
dc057fe39d | ||
|
|
9877ab131a | ||
|
|
13aba64cb7 | ||
|
|
fe9bc2dbfc | ||
|
|
c53b682852 | ||
|
|
3aca6a9e4c | ||
|
|
40fdfc0317 | ||
|
|
25d9415b0a | ||
|
|
43c8702398 | ||
|
|
ce94776dbb | ||
|
|
78ab6cd848 | ||
|
|
0a7001f868 | ||
|
|
2db7be671b | ||
|
|
b51870f7bf | ||
|
|
0227dfa7e5 | ||
|
|
05b59d9af0 | ||
|
|
04f9f3b3a8 | ||
|
|
b58be8b38c | ||
|
|
10493d754a | ||
|
|
cf28516b8b | ||
|
|
d81e25c4e4 | ||
|
|
c4612ff5d2 | ||
|
|
d70acbb47e | ||
|
|
3c977d22fe | ||
|
|
e34db2b583 | ||
|
|
71de57ac88 | ||
|
|
c7cc661018 | ||
|
|
09f39c02fe | ||
|
|
23c76a73e0 | ||
|
|
1cafe08237 | ||
|
|
45b07964ef | ||
|
|
6cc4a57b62 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,3 +5,4 @@ coverage.out
|
||||
dist/
|
||||
*.hold/
|
||||
/deploy/
|
||||
.vscode/
|
||||
|
||||
6
Makefile
6
Makefile
@@ -4,7 +4,7 @@ PROJ=holos
|
||||
ORG_PATH=github.com/holos-run
|
||||
REPO_PATH=$(ORG_PATH)/$(PROJ)
|
||||
|
||||
VERSION := $(shell grep "const Version " pkg/version/version.go | sed -E 's/.*"(.+)"$$/\1/')
|
||||
VERSION := $(shell cat pkg/version/embedded/major pkg/version/embedded/minor pkg/version/embedded/patch | xargs printf "%s.%s.%s")
|
||||
BIN_NAME := holos
|
||||
|
||||
DOCKER_REPO=quay.io/openinfrastructure/holos
|
||||
@@ -39,6 +39,10 @@ bumpmajor: ## Bump the major version.
|
||||
scripts/bump minor 0
|
||||
scripts/bump patch 0
|
||||
|
||||
.PHONY: show-version
|
||||
show-version: ## Print the full version.
|
||||
@echo $(VERSION)
|
||||
|
||||
.PHONY: tidy
|
||||
tidy: ## Tidy go module.
|
||||
go mod tidy
|
||||
|
||||
@@ -19,9 +19,10 @@ type BuildPlanSpec struct {
|
||||
}
|
||||
|
||||
type BuildPlanComponents struct {
|
||||
HelmCharts []HelmChart `json:"helmCharts,omitempty" yaml:"helmCharts,omitempty"`
|
||||
KubernetesObjects []KubernetesObjects `json:"kubernetesObjects,omitempty" yaml:"kubernetesObjects,omitempty"`
|
||||
KustomizeBuilds []KustomizeBuild `json:"kustomizeBuilds,omitempty" yaml:"kustomizeBuilds,omitempty"`
|
||||
HelmChartList []HelmChart `json:"helmChartList,omitempty" yaml:"helmChartList,omitempty"`
|
||||
KubernetesObjectsList []KubernetesObjects `json:"kubernetesObjectsList,omitempty" yaml:"kubernetesObjectsList,omitempty"`
|
||||
KustomizeBuildList []KustomizeBuild `json:"kustomizeBuildList,omitempty" yaml:"kustomizeBuildList,omitempty"`
|
||||
Resources map[string]KubernetesObjects `json:"resources,omitempty" yaml:"resources,omitempty"`
|
||||
}
|
||||
|
||||
func (bp *BuildPlan) Validate() error {
|
||||
|
||||
@@ -35,7 +35,7 @@ type Repository struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
func (hc *HelmChart) Render(ctx context.Context, path holos.PathComponent) (*Result, error) {
|
||||
func (hc *HelmChart) Render(ctx context.Context, path holos.InstancePath) (*Result, error) {
|
||||
result := Result{HolosComponent: hc.HolosComponent}
|
||||
if err := hc.helm(ctx, &result, path); err != nil {
|
||||
return nil, err
|
||||
@@ -49,7 +49,7 @@ func (hc *HelmChart) Render(ctx context.Context, path holos.PathComponent) (*Res
|
||||
|
||||
// runHelm provides the values produced by CUE to helm template and returns
|
||||
// the rendered kubernetes api objects in the result.
|
||||
func (hc *HelmChart) helm(ctx context.Context, r *Result, path holos.PathComponent) error {
|
||||
func (hc *HelmChart) helm(ctx context.Context, r *Result, path holos.InstancePath) error {
|
||||
log := logger.FromContext(ctx).With("chart", hc.Chart.Name)
|
||||
if hc.Chart.Name == "" {
|
||||
log.WarnContext(ctx, "skipping helm: no chart name specified, use a different component type")
|
||||
@@ -121,7 +121,7 @@ func (hc *HelmChart) helm(ctx context.Context, r *Result, path holos.PathCompone
|
||||
}
|
||||
|
||||
// cacheChart stores a cached copy of Chart in the chart subdirectory of path.
|
||||
func cacheChart(ctx context.Context, path holos.PathComponent, chartDir string, chart Chart) error {
|
||||
func cacheChart(ctx context.Context, path holos.InstancePath, chartDir string, chart Chart) error {
|
||||
log := logger.FromContext(ctx)
|
||||
|
||||
cacheTemp, err := os.MkdirTemp(string(path), chartDir)
|
||||
|
||||
@@ -2,6 +2,7 @@ package v1alpha1
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/holos-run/holos"
|
||||
)
|
||||
|
||||
@@ -13,7 +14,7 @@ type KubernetesObjects struct {
|
||||
}
|
||||
|
||||
// Render produces kubernetes api objects from the APIObjectMap
|
||||
func (o *KubernetesObjects) Render(ctx context.Context, path holos.PathComponent) (*Result, error) {
|
||||
func (o *KubernetesObjects) Render(ctx context.Context, path holos.InstancePath) (*Result, error) {
|
||||
result := Result{HolosComponent: o.HolosComponent}
|
||||
result.addObjectMap(ctx, o.APIObjectMap)
|
||||
return &result, nil
|
||||
|
||||
@@ -2,6 +2,7 @@ package v1alpha1
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/holos-run/holos"
|
||||
"github.com/holos-run/holos/pkg/logger"
|
||||
"github.com/holos-run/holos/pkg/util"
|
||||
@@ -29,7 +30,7 @@ type KustomizeBuild struct {
|
||||
|
||||
// Render produces a Result by executing kubectl kustomize on the holos
|
||||
// component path. Useful for processing raw yaml files.
|
||||
func (kb *KustomizeBuild) Render(ctx context.Context, path holos.PathComponent) (*Result, error) {
|
||||
func (kb *KustomizeBuild) Render(ctx context.Context, path holos.InstancePath) (*Result, error) {
|
||||
log := logger.FromContext(ctx)
|
||||
result := Result{HolosComponent: kb.HolosComponent}
|
||||
// Run kustomize.
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
package v1alpha1
|
||||
|
||||
// Label is an arbitrary unique identifier. Defined as a type for clarity and type checking.
|
||||
type Label string
|
||||
|
||||
// Kind is a kubernetes api object kind. Defined as a type for clarity and type checking.
|
||||
type Kind string
|
||||
|
||||
// APIObjectMap is the shape of marshalled api objects returned from cue to the
|
||||
// holos cli. A map is used to improve the clarity of error messages from cue.
|
||||
type APIObjectMap map[string]map[string]string
|
||||
type APIObjectMap map[Kind]map[Label]string
|
||||
|
||||
// FileContentMap is a map of file names to file contents.
|
||||
type FileContentMap map[string]string
|
||||
|
||||
@@ -2,12 +2,13 @@ package v1alpha1
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/holos-run/holos"
|
||||
)
|
||||
|
||||
type Renderer interface {
|
||||
GetKind() string
|
||||
Render(ctx context.Context, path holos.PathComponent) (*Result, error)
|
||||
Render(ctx context.Context, path holos.InstancePath) (*Result, error)
|
||||
}
|
||||
|
||||
// Render produces a Result representing the kubernetes api objects to
|
||||
@@ -16,6 +17,6 @@ type Renderer interface {
|
||||
// conceptualized as a data pipeline, for example a component may render a
|
||||
// result by first calling helm template, then passing the result through
|
||||
// kustomize, then mixing in overlay api objects.
|
||||
func Render(ctx context.Context, r Renderer, path holos.PathComponent) (*Result, error) {
|
||||
func Render(ctx context.Context, r Renderer, path holos.InstancePath) (*Result, error) {
|
||||
return r.Render(ctx, path)
|
||||
}
|
||||
|
||||
@@ -3,12 +3,13 @@ package v1alpha1
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/holos-run/holos/pkg/logger"
|
||||
"github.com/holos-run/holos/pkg/util"
|
||||
"github.com/holos-run/holos/pkg/wrapper"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
|
||||
"github.com/holos-run/holos/pkg/logger"
|
||||
"github.com/holos-run/holos/pkg/util"
|
||||
"github.com/holos-run/holos/pkg/wrapper"
|
||||
)
|
||||
|
||||
// Result is the build result for display or writing. Holos components Render the Result as a data pipeline.
|
||||
@@ -40,7 +41,7 @@ func (r *Result) AccumulatedOutput() string {
|
||||
func (r *Result) addObjectMap(ctx context.Context, objectMap APIObjectMap) {
|
||||
log := logger.FromContext(ctx)
|
||||
b := []byte(r.AccumulatedOutput())
|
||||
kinds := make([]string, 0, len(objectMap))
|
||||
kinds := make([]Kind, 0, len(objectMap))
|
||||
// Sort the keys
|
||||
for kind := range objectMap {
|
||||
kinds = append(kinds, kind)
|
||||
@@ -50,7 +51,7 @@ func (r *Result) addObjectMap(ctx context.Context, objectMap APIObjectMap) {
|
||||
for _, kind := range kinds {
|
||||
v := objectMap[kind]
|
||||
// Sort the keys
|
||||
names := make([]string, 0, len(v))
|
||||
names := make([]Label, 0, len(v))
|
||||
for name := range v {
|
||||
names = append(names, name)
|
||||
}
|
||||
|
||||
4
cmd/holos/testdata/constraints.txt
vendored
4
cmd/holos/testdata/constraints.txt
vendored
@@ -11,7 +11,7 @@ metadata: name: "jeff"
|
||||
-- foo/bar/bar.cue --
|
||||
package holos
|
||||
|
||||
spec: components: KubernetesObjects: [
|
||||
spec: components: KubernetesObjectsList: [
|
||||
#KubernetesObjects & {
|
||||
apiObjectMap: foo: bar: "bf2bc7f9-9ba0-4f9e-9bd2-9a205627eb0b"
|
||||
}
|
||||
@@ -19,7 +19,7 @@ spec: components: KubernetesObjects: [
|
||||
-- schema.cue --
|
||||
package holos
|
||||
|
||||
cluster: string @tag(cluster, string)
|
||||
_cluster: string @tag(cluster, string)
|
||||
|
||||
#KubernetesObjects: {
|
||||
apiVersion: "holos.run/v1alpha1"
|
||||
|
||||
9
cmd/holos/testdata/issue15_cue_errors.txt
vendored
9
cmd/holos/testdata/issue15_cue_errors.txt
vendored
@@ -1,6 +1,6 @@
|
||||
# Want cue errors to show files and lines
|
||||
! exec holos build .
|
||||
stderr 'apiObjectMap.foo.bar: cannot convert non-concrete value string:'
|
||||
stderr 'apiObjectMap.foo.bar: cannot convert incomplete value'
|
||||
stderr '/component.cue:\d+:\d+$'
|
||||
|
||||
-- cue.mod --
|
||||
@@ -8,9 +8,10 @@ package holos
|
||||
-- component.cue --
|
||||
package holos
|
||||
|
||||
_cluster: string @tag(cluster, string)
|
||||
|
||||
apiVersion: "holos.run/v1alpha1"
|
||||
kind: "BuildPlan"
|
||||
cluster: string @tag(cluster, string)
|
||||
spec: components: KubernetesObjectsList: [{apiObjectMap: foo: bar: _baz}]
|
||||
|
||||
baz: string
|
||||
spec: components: KubernetesObjects: [{apiObjectMap: foo: bar: baz}]
|
||||
_baz: string
|
||||
|
||||
@@ -10,9 +10,9 @@ package holos
|
||||
|
||||
apiVersion: "holos.run/v1alpha1"
|
||||
kind: "BuildPlan"
|
||||
spec: components: KubernetesObjects: [{apiObjectMap: #APIObjects.apiObjectMap}]
|
||||
spec: components: KubernetesObjectsList: [{apiObjectMap: #APIObjects.apiObjectMap}]
|
||||
|
||||
cluster: string @tag(cluster, string)
|
||||
_cluster: string @tag(cluster, string)
|
||||
|
||||
#SecretStore: {
|
||||
kind: string
|
||||
|
||||
@@ -11,9 +11,9 @@ package holos
|
||||
|
||||
apiVersion: "holos.run/v1alpha1"
|
||||
kind: "BuildPlan"
|
||||
spec: components: HelmCharts: [{apiObjectMap: #APIObjects.apiObjectMap}]
|
||||
spec: components: HelmChartList: [{apiObjectMap: #APIObjects.apiObjectMap}]
|
||||
|
||||
cluster: string @tag(cluster, string)
|
||||
_cluster: string @tag(cluster, string)
|
||||
|
||||
#SecretStore: {
|
||||
kind: string
|
||||
|
||||
4
cmd/holos/testdata/issue33_helm_stderr.txt
vendored
4
cmd/holos/testdata/issue33_helm_stderr.txt
vendored
@@ -9,9 +9,9 @@ package holos
|
||||
|
||||
apiVersion: "holos.run/v1alpha1"
|
||||
kind: "BuildPlan"
|
||||
spec: components: HelmCharts: [_HelmChart]
|
||||
spec: components: HelmChartList: [_HelmChart]
|
||||
|
||||
cluster: string @tag(cluster, string)
|
||||
_cluster: string @tag(cluster, string)
|
||||
|
||||
_HelmChart: {
|
||||
apiVersion: "holos.run/v1alpha1"
|
||||
|
||||
@@ -9,11 +9,11 @@ package holos
|
||||
-- component.cue --
|
||||
package holos
|
||||
|
||||
cluster: string @tag(cluster, string)
|
||||
_cluster: string @tag(cluster, string)
|
||||
|
||||
apiVersion: "holos.run/v1alpha1"
|
||||
kind: "BuildPlan"
|
||||
spec: components: KustomizeBuilds: [{metadata: name: "kstest"}]
|
||||
spec: components: KustomizeBuildList: [{metadata: name: "kstest"}]
|
||||
|
||||
-- kustomization.yaml --
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
|
||||
14
cmd/holos/testdata/issue72_disallow_unknown_fields.txt
vendored
Normal file
14
cmd/holos/testdata/issue72_disallow_unknown_fields.txt
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
# https://github.com/holos-run/holos/issues/72
|
||||
# Want holos to fail on unknown fields to catch typos and aid refactors
|
||||
! exec holos build .
|
||||
stderr 'unknown field \\"TypoKubernetesObjectsList\\"'
|
||||
|
||||
-- cue.mod --
|
||||
package holos
|
||||
-- component.cue --
|
||||
package holos
|
||||
_cluster: string @tag(cluster, string)
|
||||
|
||||
apiVersion: "holos.run/v1alpha1"
|
||||
kind: "BuildPlan"
|
||||
spec: components: TypoKubernetesObjectsList: []
|
||||
@@ -19,7 +19,8 @@ package v1alpha1
|
||||
}
|
||||
|
||||
#BuildPlanComponents: {
|
||||
helmCharts?: [...#HelmChart] @go(HelmCharts,[]HelmChart)
|
||||
kubernetesObjects?: [...#KubernetesObjects] @go(KubernetesObjects,[]KubernetesObjects)
|
||||
kustomizeBuilds?: [...#KustomizeBuild] @go(KustomizeBuilds,[]KustomizeBuild)
|
||||
helmChartList?: [...#HelmChart] @go(HelmChartList,[]HelmChart)
|
||||
kubernetesObjectsList?: [...#KubernetesObjects] @go(KubernetesObjectsList,[]KubernetesObjects)
|
||||
kustomizeBuildList?: [...#KustomizeBuild] @go(KustomizeBuildList,[]KustomizeBuild)
|
||||
resources?: {[string]: #KubernetesObjects} @go(Resources,map[string]KubernetesObjects)
|
||||
}
|
||||
|
||||
@@ -11,5 +11,5 @@ package v1alpha1
|
||||
// ChartDir is the directory name created in the holos component directory to cache a chart.
|
||||
#ChartDir: "vendor"
|
||||
|
||||
// ResourcesFile is the file name used to store component output when post processing with kustomize.
|
||||
// ResourcesFile is the file name used to store component output when post-processing with kustomize.
|
||||
#ResourcesFile: "resources.yaml"
|
||||
|
||||
@@ -4,6 +4,12 @@
|
||||
|
||||
package v1alpha1
|
||||
|
||||
// Label is an arbitrary unique identifier. Defined as a type for clarity and type checking.
|
||||
#Label: string
|
||||
|
||||
// Kind is a kubernetes api object kind. Defined as a type for clarity and type checking.
|
||||
#Kind: string
|
||||
|
||||
// APIObjectMap is the shape of marshalled api objects returned from cue to the
|
||||
// holos cli. A map is used to improve the clarity of error messages from cue.
|
||||
#APIObjectMap: {[string]: [string]: string}
|
||||
|
||||
@@ -306,19 +306,10 @@ import "strings"
|
||||
// "value"` for prefix-based match - `regex: "value"` for RE2
|
||||
// style regex-based match
|
||||
// (https://github.com/google/re2/wiki/Syntax).
|
||||
uri?: ({} | {
|
||||
exact: _
|
||||
} | {
|
||||
prefix: _
|
||||
} | {
|
||||
regex: _
|
||||
}) & {
|
||||
uri?: {
|
||||
exact?: string
|
||||
prefix?: string
|
||||
|
||||
// RE2 style regex-based match
|
||||
// (https://github.com/google/re2/wiki/Syntax).
|
||||
regex?: string
|
||||
regex?: string
|
||||
}
|
||||
|
||||
// withoutHeader has the same syntax with the header, but has
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
package v1alpha1
|
||||
|
||||
#HolosComponent: metadata: name: string
|
||||
|
||||
@@ -7,14 +7,21 @@ import "encoding/yaml"
|
||||
// apiObjects holds each the api objects produced by cue.
|
||||
apiObjects: {
|
||||
[Kind=_]: {
|
||||
[Name=_]: {
|
||||
[string]: {
|
||||
kind: Kind
|
||||
...
|
||||
}
|
||||
}
|
||||
Namespace?: [Name=_]: #Namespace & {metadata: name: Name}
|
||||
ExternalSecret?: [Name=_]: #ExternalSecret & {_name: Name}
|
||||
VirtualService?: [Name=_]: #VirtualService & {metadata: name: Name}
|
||||
Issuer?: [Name=_]: #Issuer & {metadata: name: Name}
|
||||
Gateway?: [Name=_]: #Gateway & {metadata: name: Name}
|
||||
ConfigMap?: [Name=_]: #ConfigMap & {metadata: name: Name}
|
||||
|
||||
Deployment?: [_]: #Deployment
|
||||
RequestAuthentication?: [_]: #RequestAuthentication
|
||||
AuthorizationPolicy?: [_]: #AuthorizationPolicy
|
||||
}
|
||||
|
||||
// apiObjectMap holds the marshalled representation of apiObjects
|
||||
|
||||
@@ -24,6 +24,8 @@ let DependsOn = {[Name=_]: name: string & Name}
|
||||
metadata: name: string
|
||||
#namelen: len(metadata.name) & >=1
|
||||
let Name = metadata.name
|
||||
|
||||
// TODO: ksContent needs to be component scoped, not instance scoped.
|
||||
ksContent: yaml.Marshal(#Kustomization & {
|
||||
_dependsOn: DEPENDS_ON
|
||||
metadata: name: Name
|
||||
@@ -47,7 +49,7 @@ let DependsOn = {[Name=_]: name: string & Name}
|
||||
|
||||
// Holos component types.
|
||||
#HelmChart: #HolosComponent & h.#HelmChart & {
|
||||
_values: _
|
||||
_values: {...}
|
||||
_kustomizeFiles: #KustomizeFiles
|
||||
|
||||
// Render the values to yaml for holos to provide to helm.
|
||||
@@ -57,6 +59,11 @@ let DependsOn = {[Name=_]: name: string & Name}
|
||||
resourcesFile: h.#ResourcesFile
|
||||
// kustomizeFiles represents the files in a kustomize directory tree.
|
||||
kustomizeFiles: _kustomizeFiles.Files
|
||||
|
||||
chart: h.#Chart & {
|
||||
name: string
|
||||
release: string | *name
|
||||
}
|
||||
}
|
||||
#KubernetesObjects: #HolosComponent & h.#KubernetesObjects
|
||||
#KustomizeBuild: #HolosComponent & h.#KustomizeBuild
|
||||
@@ -93,10 +100,6 @@ let DependsOn = {[Name=_]: name: string & Name}
|
||||
}
|
||||
}
|
||||
|
||||
// #KustomizeTree represents a kustomize build.
|
||||
#KustomizeFiles: {
|
||||
}
|
||||
|
||||
// #Kustomize represents the kustomize post processor.
|
||||
#Kustomize: kc.#Kustomization & {
|
||||
_patches: {[_]: kc.#Patch}
|
||||
@@ -109,3 +112,6 @@ let DependsOn = {[Name=_]: name: string & Name}
|
||||
patches: [for v in _patches {v}]
|
||||
}
|
||||
}
|
||||
|
||||
// So components don't need to import the package.
|
||||
#Patch: kc.#Patch
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,17 @@ let SecretNames = {
|
||||
},
|
||||
]
|
||||
|
||||
#KubernetesObjects & {
|
||||
spec: components: KubernetesObjectsList: [
|
||||
#KubernetesObjects & {
|
||||
metadata: name: "prod-iam-postgres-certs"
|
||||
|
||||
_dependsOn: "prod-secrets-stores": _
|
||||
|
||||
apiObjectMap: OBJECTS.apiObjectMap
|
||||
},
|
||||
]
|
||||
|
||||
let OBJECTS = #APIObjects & {
|
||||
apiObjects: {
|
||||
for s in SecretNames {
|
||||
ExternalSecret: "\(s.name)": _
|
||||
|
||||
@@ -33,7 +33,17 @@ let RestoreOptions = []
|
||||
},
|
||||
]
|
||||
|
||||
#KubernetesObjects & {
|
||||
spec: components: KubernetesObjectsList: [
|
||||
#KubernetesObjects & {
|
||||
metadata: name: "prod-iam-postgres"
|
||||
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
_dependsOn: "prod-iam-postgres-certs": _
|
||||
apiObjectMap: OBJECTS.apiObjectMap
|
||||
},
|
||||
]
|
||||
|
||||
let OBJECTS = #APIObjects & {
|
||||
apiObjects: {
|
||||
ExternalSecret: "\(S3Secret)": _
|
||||
PostgresCluster: db: #PostgresCluster & HighlyAvailable & {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package holos
|
||||
|
||||
#InstancePrefix: "prod-iam"
|
||||
#TargetNamespace: #InstancePrefix + "-zitadel"
|
||||
|
||||
// _DBName is the database name used across multiple holos components in this project
|
||||
|
||||
@@ -4,50 +4,30 @@ import "encoding/yaml"
|
||||
|
||||
let Name = "zitadel"
|
||||
#InputKeys: component: Name
|
||||
#DependsOn: postgres: _
|
||||
|
||||
// Upstream helm chart doesn't specify the namespace field for all resources.
|
||||
#Kustomization: spec: {
|
||||
targetNamespace: #TargetNamespace
|
||||
wait: false
|
||||
}
|
||||
spec: components: HelmChartList: [
|
||||
#HelmChart & {
|
||||
metadata: name: "\(#InstancePrefix)-zitadel"
|
||||
|
||||
if #IsPrimaryCluster == true {
|
||||
#Kustomization: spec: healthChecks: [
|
||||
{
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
name: Name
|
||||
namespace: #TargetNamespace
|
||||
},
|
||||
{
|
||||
apiVersion: "batch/v1"
|
||||
kind: "Job"
|
||||
name: "\(Name)-init"
|
||||
namespace: #TargetNamespace
|
||||
},
|
||||
{
|
||||
apiVersion: "batch/v1"
|
||||
kind: "Job"
|
||||
name: "\(Name)-setup"
|
||||
namespace: #TargetNamespace
|
||||
},
|
||||
]
|
||||
}
|
||||
_dependsOn: "prod-secrets-stores": _
|
||||
_dependsOn: "\(#InstancePrefix)-postgres": _
|
||||
|
||||
#HelmChart & {
|
||||
namespace: #TargetNamespace
|
||||
enableHooks: true
|
||||
chart: {
|
||||
name: Name
|
||||
version: "7.9.0"
|
||||
repository: {
|
||||
name: Name
|
||||
url: "https://charts.zitadel.com"
|
||||
namespace: #TargetNamespace
|
||||
enableHooks: true
|
||||
chart: {
|
||||
name: Name
|
||||
version: "7.9.0"
|
||||
repository: {
|
||||
name: Name
|
||||
url: "https://charts.zitadel.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
values: #Values
|
||||
_values: #Values
|
||||
apiObjectMap: OBJECTS.apiObjectMap
|
||||
},
|
||||
]
|
||||
|
||||
let OBJECTS = #APIObjects & {
|
||||
apiObjects: {
|
||||
ExternalSecret: "zitadel-masterkey": _
|
||||
VirtualService: "\(Name)": {
|
||||
@@ -97,8 +77,7 @@ let CAPatch = #Patch & {
|
||||
patch: yaml.Marshal(DatabaseCACertPatch)
|
||||
}
|
||||
|
||||
// TODO: Replace with #Kustomize & { _patches: foo: {} }
|
||||
#KustomizePatches: {
|
||||
#Kustomize: _patches: {
|
||||
mesh: {
|
||||
target: {
|
||||
group: "apps"
|
||||
@@ -163,3 +142,32 @@ let CAPatch = #Patch & {
|
||||
}
|
||||
|
||||
let DisableFluxPatch = [{op: "replace", path: "/metadata/annotations/kustomize.toolkit.fluxcd.io~1reconcile", value: "disabled"}]
|
||||
|
||||
// Upstream helm chart doesn't specify the namespace field for all resources.
|
||||
#Kustomization: spec: {
|
||||
targetNamespace: #TargetNamespace
|
||||
wait: false
|
||||
}
|
||||
|
||||
if #IsPrimaryCluster == true {
|
||||
#Kustomization: spec: healthChecks: [
|
||||
{
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
name: Name
|
||||
namespace: #TargetNamespace
|
||||
},
|
||||
{
|
||||
apiVersion: "batch/v1"
|
||||
kind: "Job"
|
||||
name: "\(Name)-init"
|
||||
namespace: #TargetNamespace
|
||||
},
|
||||
{
|
||||
apiVersion: "batch/v1"
|
||||
kind: "Job"
|
||||
name: "\(Name)-setup"
|
||||
namespace: #TargetNamespace
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ let GitHubConfigSecret = "controller-manager"
|
||||
// Just sync the external secret, don't configure the scale set
|
||||
// Work around https://github.com/actions/actions-runner-controller/issues/3351
|
||||
if #IsPrimaryCluster == false {
|
||||
spec: components: KubernetesObjects: [
|
||||
spec: components: KubernetesObjectsList: [
|
||||
#KubernetesObjects & {
|
||||
metadata: name: "prod-github-arc-runner"
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
@@ -23,7 +23,7 @@ if #IsPrimaryCluster == false {
|
||||
|
||||
// Put the scale set on the primary cluster.
|
||||
if #IsPrimaryCluster == true {
|
||||
spec: components: HelmCharts: [
|
||||
spec: components: HelmChartList: [
|
||||
#HelmChart & {
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
metadata: name: "prod-github-arc-runner"
|
||||
|
||||
@@ -3,7 +3,7 @@ package holos
|
||||
#TargetNamespace: #ARCSystemNamespace
|
||||
#InputKeys: component: "arc-system"
|
||||
|
||||
spec: components: HelmCharts: [
|
||||
spec: components: HelmChartList: [
|
||||
#HelmChart & {
|
||||
metadata: name: "prod-github-arc-system"
|
||||
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
package holos
|
||||
|
||||
import "list"
|
||||
|
||||
spec: components: KubernetesObjects: [
|
||||
spec: components: KubernetesObjectsList: [
|
||||
#KubernetesObjects & {
|
||||
metadata: name: "prod-secrets-namespaces"
|
||||
apiObjectMap: (#APIObjects & {
|
||||
apiObjects: {
|
||||
// #ManagedNamespaces is the set of all namespaces across all clusters in the platform.
|
||||
for k, ns in #ManagedNamespaces {
|
||||
if list.Contains(ns.clusterNames, #ClusterName) {
|
||||
if ns.clusters[#ClusterName] != _|_ {
|
||||
Namespace: "\(k)": #Namespace & ns.namespace
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
package holos
|
||||
|
||||
#InputKeys: component: "istio-base"
|
||||
#TargetNamespace: "istio-system"
|
||||
spec: components: HelmChartList: [
|
||||
#HelmChart & {
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
|
||||
#HelmChart & {
|
||||
namespace: #TargetNamespace
|
||||
chart: {
|
||||
name: "base"
|
||||
version: "1.20.3"
|
||||
repository: {
|
||||
name: "istio"
|
||||
url: "https://istio-release.storage.googleapis.com/charts"
|
||||
metadata: name: "prod-mesh-istio-base"
|
||||
namespace: "istio-system"
|
||||
chart: {
|
||||
name: "base"
|
||||
version: #IstioVersion
|
||||
repository: {
|
||||
name: "istio"
|
||||
url: "https://istio-release.storage.googleapis.com/charts"
|
||||
}
|
||||
}
|
||||
}
|
||||
values: #IstioValues
|
||||
}
|
||||
_values: #IstioValues
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package holos
|
||||
|
||||
#InputKeys: component: "cni"
|
||||
#TargetNamespace: "kube-system"
|
||||
spec: components: HelmChartList: [
|
||||
#HelmChart & {
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
_dependsOn: "prod-mesh-istio-base": _
|
||||
|
||||
#HelmChart & {
|
||||
namespace: #TargetNamespace
|
||||
chart: name: "cni"
|
||||
values: #IstioValues
|
||||
}
|
||||
_values: #IstioValues
|
||||
metadata: name: "\(#InstancePrefix)-\(chart.name)"
|
||||
namespace: "kube-system"
|
||||
chart: name: "cni"
|
||||
},
|
||||
]
|
||||
|
||||
@@ -4,15 +4,23 @@ import "list"
|
||||
|
||||
// The primary istio Gateway, named default
|
||||
let Name = "gateway"
|
||||
|
||||
#InputKeys: component: Name
|
||||
|
||||
#TargetNamespace: "istio-ingress"
|
||||
#DependsOn: _IngressGateway
|
||||
|
||||
let LoginCert = #PlatformCerts.login
|
||||
|
||||
#KubernetesObjects & {
|
||||
spec: components: KubernetesObjectsList: [
|
||||
#KubernetesObjects & {
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
_dependsOn: "prod-mesh-istio-base": _
|
||||
_dependsOn: "prod-mesh-ingress": _
|
||||
|
||||
metadata: name: "\(#InstancePrefix)-\(Name)"
|
||||
apiObjectMap: OBJECTS.apiObjectMap
|
||||
},
|
||||
]
|
||||
|
||||
let OBJECTS = #APIObjects & {
|
||||
apiObjects: {
|
||||
ExternalSecret: login: #ExternalSecret & {
|
||||
_name: "login"
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
package holos
|
||||
|
||||
let Name = "httpbin"
|
||||
let ComponentName = "\(#InstancePrefix)-\(Name)"
|
||||
|
||||
let SecretName = #InputKeys.cluster + "-" + Name
|
||||
let MatchLabels = {app: Name} & #SelectorLabels
|
||||
let MatchLabels = {
|
||||
app: Name
|
||||
"app.kubernetes.io/instance": ComponentName
|
||||
}
|
||||
let Metadata = {
|
||||
name: Name
|
||||
namespace: #TargetNamespace
|
||||
@@ -12,11 +17,22 @@ let Metadata = {
|
||||
#InputKeys: component: Name
|
||||
|
||||
#TargetNamespace: "istio-ingress"
|
||||
#DependsOn: _IngressGateway
|
||||
|
||||
let Cert = #PlatformCerts[SecretName]
|
||||
|
||||
#KubernetesObjects & {
|
||||
spec: components: KubernetesObjectsList: [
|
||||
#KubernetesObjects & {
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
_dependsOn: "\(#InstancePrefix)-istio-base": _
|
||||
_dependsOn: "\(#InstancePrefix)-ingress": _
|
||||
|
||||
metadata: name: ComponentName
|
||||
|
||||
apiObjectMap: OBJECTS.apiObjectMap
|
||||
},
|
||||
]
|
||||
|
||||
let OBJECTS = #APIObjects & {
|
||||
apiObjects: {
|
||||
ExternalSecret: "\(Cert.spec.secretName)": _
|
||||
Deployment: httpbin: #Deployment & {
|
||||
@@ -24,7 +40,6 @@ let Cert = #PlatformCerts[SecretName]
|
||||
spec: selector: matchLabels: MatchLabels
|
||||
spec: template: {
|
||||
metadata: labels: MatchLabels
|
||||
metadata: labels: #CommonLabels
|
||||
metadata: labels: #IstioSidecar
|
||||
spec: securityContext: seccompProfile: type: "RuntimeDefault"
|
||||
spec: containers: [{
|
||||
@@ -35,8 +50,8 @@ let Cert = #PlatformCerts[SecretName]
|
||||
seccompProfile: type: "RuntimeDefault"
|
||||
allowPrivilegeEscalation: false
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1337
|
||||
runAsGroup: 1337
|
||||
runAsUser: 8192
|
||||
runAsGroup: 8192
|
||||
capabilities: drop: ["ALL"]
|
||||
}}]
|
||||
}
|
||||
@@ -54,7 +69,7 @@ let Cert = #PlatformCerts[SecretName]
|
||||
spec: servers: [
|
||||
{
|
||||
hosts: [for host in Cert.spec.dnsNames {"\(#TargetNamespace)/\(host)"}]
|
||||
port: name: "https-\(#InstanceName)"
|
||||
port: name: "https-\(ComponentName)"
|
||||
port: number: 443
|
||||
port: protocol: "HTTPS"
|
||||
tls: credentialName: Cert.spec.secretName
|
||||
|
||||
@@ -2,50 +2,62 @@ package holos
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
#InputKeys: component: "ingress"
|
||||
#TargetNamespace: "istio-ingress"
|
||||
#DependsOn: _IstioD
|
||||
let ComponentName = "\(#InstancePrefix)-ingress"
|
||||
|
||||
#HelmChart & {
|
||||
chart: name: "gateway"
|
||||
namespace: #TargetNamespace
|
||||
values: #GatewayValues & {
|
||||
// This component expects the load balancer to send the PROXY protocol header.
|
||||
// Refer to: https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.2/guide/service/annotations/#proxy-protocol-v2
|
||||
podAnnotations: "proxy.istio.io/config": json.Marshal(_ProxyProtocol)
|
||||
// TODO This configuration is specific to the OIS Metal NLB, refactor it out to the metal collection.
|
||||
service: {
|
||||
type: "NodePort"
|
||||
annotations: "service.beta.kubernetes.io/aws-load-balancer-proxy-protocol": "*"
|
||||
externalTrafficPolicy: "Local"
|
||||
// Add 30000 to the port to get the Nodeport
|
||||
ports: [
|
||||
{
|
||||
name: "status-port"
|
||||
port: 15021
|
||||
protocol: "TCP"
|
||||
targetPort: 15021
|
||||
nodePort: 30021
|
||||
},
|
||||
{
|
||||
name: "http2"
|
||||
port: 80
|
||||
protocol: "TCP"
|
||||
targetPort: 80
|
||||
nodePort: 30080
|
||||
},
|
||||
{
|
||||
name: "https"
|
||||
port: 443
|
||||
protocol: "TCP"
|
||||
targetPort: 443
|
||||
nodePort: 30443
|
||||
},
|
||||
]
|
||||
#TargetNamespace: "istio-ingress"
|
||||
|
||||
spec: components: HelmChartList: [
|
||||
#HelmChart & {
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
_dependsOn: "\(#InstancePrefix)-istio-base": _
|
||||
_dependsOn: "\(#InstancePrefix)-istiod": _
|
||||
|
||||
metadata: name: ComponentName
|
||||
|
||||
chart: name: "gateway"
|
||||
namespace: #TargetNamespace
|
||||
_values: #GatewayValues & {
|
||||
// This component expects the load balancer to send the PROXY protocol header.
|
||||
// Refer to: https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.2/guide/service/annotations/#proxy-protocol-v2
|
||||
podAnnotations: "proxy.istio.io/config": json.Marshal(_ProxyProtocol)
|
||||
// TODO This configuration is specific to the OIS Metal NLB, refactor it out to the metal collection.
|
||||
service: {
|
||||
type: "NodePort"
|
||||
annotations: "service.beta.kubernetes.io/aws-load-balancer-proxy-protocol": "*"
|
||||
externalTrafficPolicy: "Local"
|
||||
// Add 30000 to the port to get the Nodeport
|
||||
ports: [
|
||||
{
|
||||
name: "status-port"
|
||||
port: 15021
|
||||
protocol: "TCP"
|
||||
targetPort: 15021
|
||||
nodePort: 30021
|
||||
},
|
||||
{
|
||||
name: "http2"
|
||||
port: 80
|
||||
protocol: "TCP"
|
||||
targetPort: 80
|
||||
nodePort: 30080
|
||||
},
|
||||
{
|
||||
name: "https"
|
||||
port: 443
|
||||
protocol: "TCP"
|
||||
targetPort: 443
|
||||
nodePort: 30443
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
apiObjects: _APIObjects
|
||||
}
|
||||
apiObjectMap: OBJECTS.apiObjectMap
|
||||
// Auth Proxy
|
||||
apiObjectMap: _IngressAuthProxy.Deployment.apiObjectMap
|
||||
// Auth Policy
|
||||
apiObjectMap: _IngressAuthProxy.Policy.apiObjectMap
|
||||
},
|
||||
]
|
||||
|
||||
_ProxyProtocol: gatewayTopology: proxyProtocol: {}
|
||||
|
||||
@@ -60,36 +72,82 @@ let RedirectMetaName = {
|
||||
namespace: #TargetNamespace
|
||||
}
|
||||
|
||||
// https-redirect
|
||||
_APIObjects: {
|
||||
Gateway: {
|
||||
"\(RedirectMetaName.name)": #Gateway & {
|
||||
metadata: RedirectMetaName
|
||||
spec: selector: GatewayLabels
|
||||
spec: servers: [{
|
||||
port: {
|
||||
number: 80
|
||||
name: "http2"
|
||||
protocol: "HTTP2"
|
||||
}
|
||||
hosts: ["*"]
|
||||
// handled by the VirtualService
|
||||
tls: httpsRedirect: false
|
||||
}]
|
||||
let OBJECTS = #APIObjects & {
|
||||
apiObjects: {
|
||||
Gateway: {
|
||||
"\(RedirectMetaName.name)": #Gateway & {
|
||||
metadata: RedirectMetaName
|
||||
spec: selector: GatewayLabels
|
||||
spec: servers: [{
|
||||
port: {
|
||||
number: 80
|
||||
name: "http2"
|
||||
protocol: "HTTP2"
|
||||
}
|
||||
hosts: ["*"]
|
||||
// handled by the VirtualService
|
||||
tls: httpsRedirect: false
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
VirtualService: {
|
||||
"\(RedirectMetaName.name)": #VirtualService & {
|
||||
metadata: RedirectMetaName
|
||||
spec: hosts: ["*"]
|
||||
spec: gateways: [RedirectMetaName.name]
|
||||
spec: http: [{
|
||||
match: [{withoutHeaders: ":path": prefix: "/.well-known/acme-challenge/"}]
|
||||
redirect: {
|
||||
scheme: "https"
|
||||
redirectCode: 302
|
||||
VirtualService: {
|
||||
"\(RedirectMetaName.name)": #VirtualService & {
|
||||
metadata: RedirectMetaName
|
||||
spec: hosts: ["*"]
|
||||
spec: gateways: [RedirectMetaName.name]
|
||||
spec: http: [{
|
||||
match: [{withoutHeaders: ":path": prefix: "/.well-known/acme-challenge/"}]
|
||||
redirect: {
|
||||
scheme: "https"
|
||||
redirectCode: 302
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
Deployment: {
|
||||
loopback: #Deployment & {
|
||||
_description: LoopbackDescription
|
||||
metadata: LoopbackMetaName
|
||||
spec: {
|
||||
selector: matchLabels: LoopbackLabels
|
||||
template: {
|
||||
metadata: {
|
||||
annotations: "inject.istio.io/templates": "gateway"
|
||||
annotations: #Description & {
|
||||
_Description: LoopbackDescription
|
||||
}
|
||||
labels: LoopbackLabels & {"sidecar.istio.io/inject": "true"}
|
||||
}
|
||||
spec: {
|
||||
serviceAccountName: "istio-ingressgateway"
|
||||
// Allow binding to all ports (such as 80 and 443)
|
||||
securityContext: {
|
||||
runAsNonRoot: true
|
||||
seccompProfile: type: "RuntimeDefault"
|
||||
sysctls: [{name: "net.ipv4.ip_unprivileged_port_start", value: "0"}]
|
||||
}
|
||||
containers: [{
|
||||
name: "istio-proxy"
|
||||
image: "auto" // Managed by istiod
|
||||
securityContext: {
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities: drop: ["ALL"]
|
||||
runAsUser: 1337
|
||||
runAsGroup: 1337
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
Service: {
|
||||
loopback: #Service & {
|
||||
_description: LoopbackDescription
|
||||
metadata: LoopbackMetaName
|
||||
spec: selector: LoopbackLabels
|
||||
spec: ports: [{port: 80, name: "http"}, {port: 443, name: "https"}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,52 +162,3 @@ let LoopbackMetaName = {
|
||||
name: LoopbackName
|
||||
namespace: #TargetNamespace
|
||||
}
|
||||
|
||||
// istio-ingressgateway-loopback
|
||||
_APIObjects: {
|
||||
Deployment: {
|
||||
loopback: #Deployment & {
|
||||
_description: LoopbackDescription
|
||||
metadata: LoopbackMetaName
|
||||
spec: {
|
||||
selector: matchLabels: LoopbackLabels
|
||||
template: {
|
||||
metadata: {
|
||||
annotations: "inject.istio.io/templates": "gateway"
|
||||
annotations: #Description & {
|
||||
_Description: LoopbackDescription
|
||||
}
|
||||
labels: LoopbackLabels & {"sidecar.istio.io/inject": "true"}
|
||||
}
|
||||
spec: {
|
||||
serviceAccountName: "istio-ingressgateway"
|
||||
// Allow binding to all ports (such as 80 and 443)
|
||||
securityContext: {
|
||||
runAsNonRoot: true
|
||||
seccompProfile: type: "RuntimeDefault"
|
||||
sysctls: [{name: "net.ipv4.ip_unprivileged_port_start", value: "0"}]
|
||||
}
|
||||
containers: [{
|
||||
name: "istio-proxy"
|
||||
image: "auto" // Managed by istiod
|
||||
securityContext: {
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities: drop: ["ALL"]
|
||||
runAsUser: 1337
|
||||
runAsGroup: 1337
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Service: {
|
||||
loopback: #Service & {
|
||||
_description: LoopbackDescription
|
||||
metadata: LoopbackMetaName
|
||||
spec: selector: LoopbackLabels
|
||||
spec: ports: [{port: 80, name: "http"}, {port: 443, name: "https"}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
package holos
|
||||
|
||||
#DependsOn: _IstioBase
|
||||
|
||||
#HelmChart: {
|
||||
chart: {
|
||||
version: "1.20.3"
|
||||
version: #IstioVersion
|
||||
repository: {
|
||||
name: "istio"
|
||||
url: "https://istio-release.storage.googleapis.com/charts"
|
||||
|
||||
@@ -5,22 +5,28 @@ import "encoding/yaml"
|
||||
#InputKeys: component: "istiod"
|
||||
#TargetNamespace: "istio-system"
|
||||
|
||||
#HelmChart & {
|
||||
namespace: #TargetNamespace
|
||||
chart: {
|
||||
name: "istiod"
|
||||
}
|
||||
values: #IstioValues & {
|
||||
pilot: {
|
||||
// The istio meshconfig ConfigMap is handled in the holos component instead of
|
||||
// the upstream chart so extension providers can be collected from holos data.
|
||||
configMap: false
|
||||
// Set to `type: RuntimeDefault` to use the default profile if available.
|
||||
seccompProfile: type: "RuntimeDefault"
|
||||
spec: components: HelmChartList: [
|
||||
#HelmChart & {
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
_dependsOn: "\(#InstancePrefix)-istio-base": _
|
||||
|
||||
metadata: name: "prod-mesh-istiod"
|
||||
chart: name: "istiod"
|
||||
namespace: #TargetNamespace
|
||||
_values: #IstioValues & {
|
||||
pilot: {
|
||||
// The istio meshconfig ConfigMap is handled in the holos component instead of
|
||||
// the upstream chart so extension providers can be collected from holos data.
|
||||
configMap: false
|
||||
// Set to `type: RuntimeDefault` to use the default profile if available.
|
||||
seccompProfile: type: "RuntimeDefault"
|
||||
}
|
||||
}
|
||||
}
|
||||
apiObjects: ConfigMap: istio: #IstioConfigMap
|
||||
}
|
||||
apiObjectMap: OBJECTS.apiObjectMap
|
||||
},
|
||||
]
|
||||
|
||||
let OBJECTS = #APIObjects & {apiObjects: ConfigMap: istio: #IstioConfigMap}
|
||||
|
||||
#IstioConfigMap: #ConfigMap & {
|
||||
metadata: {
|
||||
|
||||
@@ -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,14 +1,281 @@
|
||||
package holos
|
||||
|
||||
// Components under this directory are part of this collection
|
||||
#InputKeys: project: "mesh"
|
||||
import "encoding/yaml"
|
||||
|
||||
// Shared dependencies for all components in this collection.
|
||||
#DependsOn: _Namespaces
|
||||
#InstancePrefix: "prod-mesh"
|
||||
|
||||
// Common Dependencies
|
||||
_CertManager: CertManager: name: "\(#InstancePrefix)-certmanager"
|
||||
_Namespaces: Namespaces: name: "\(#StageName)-secrets-namespaces"
|
||||
_IstioBase: IstioBase: name: "\(#InstancePrefix)-istio-base"
|
||||
_IstioD: IstioD: name: "\(#InstancePrefix)-istiod"
|
||||
_IngressGateway: IngressGateway: name: "\(#InstancePrefix)-ingress"
|
||||
#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 | *""
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package holos
|
||||
|
||||
#DependsOn: Namespaces: name: "prod-secrets-namespaces"
|
||||
#DependsOn: CRDS: name: "\(#InstancePrefix)-crds"
|
||||
#InputKeys: component: "controller"
|
||||
{} & #KustomizeBuild
|
||||
spec: components: KustomizeBuildList: [
|
||||
#KustomizeBuild & {
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
_dependsOn: "prod-pgo-crds": _
|
||||
|
||||
metadata: name: "prod-pgo-controller"
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package holos
|
||||
|
||||
// Refer to https://github.com/CrunchyData/postgres-operator-examples/tree/main/kustomize/install/crd
|
||||
|
||||
#InputKeys: component: "crds"
|
||||
{} & #KustomizeBuild
|
||||
spec: components: KustomizeBuildList: [
|
||||
#KustomizeBuild & {
|
||||
metadata: name: "prod-pgo-crds"
|
||||
},
|
||||
]
|
||||
|
||||
@@ -2,8 +2,6 @@ package holos
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
#DependsOn: _ESO
|
||||
|
||||
#InputKeys: {
|
||||
project: "secrets"
|
||||
component: "eso-creds-refresher"
|
||||
@@ -11,8 +9,17 @@ import "encoding/json"
|
||||
|
||||
#TargetNamespace: #CredsRefresher.namespace
|
||||
|
||||
// output kubernetes api objects for holos
|
||||
#KubernetesObjects & {
|
||||
spec: components: KubernetesObjectsList: [
|
||||
#KubernetesObjects & {
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
_dependsOn: "prod-secrets-eso": _
|
||||
|
||||
metadata: name: "prod-secrets-eso-creds-refresher"
|
||||
apiObjectMap: OBJECTS.apiObjectMap
|
||||
},
|
||||
]
|
||||
|
||||
let OBJECTS = #APIObjects & {
|
||||
apiObjects: {
|
||||
for obj in #CredsRefresherService.objects {
|
||||
let Kind = obj.kind
|
||||
|
||||
@@ -3,26 +3,22 @@ package holos
|
||||
// Manages the External Secrets Operator from the official upstream Helm chart.
|
||||
|
||||
#TargetNamespace: "external-secrets"
|
||||
|
||||
#InputKeys: component: "eso"
|
||||
|
||||
#InputKeys: {
|
||||
project: "secrets"
|
||||
service: "eso"
|
||||
}
|
||||
|
||||
#Kustomization: spec: targetNamespace: #TargetNamespace
|
||||
#DependsOn: Namespaces: name: #InstancePrefix + "-namespaces"
|
||||
|
||||
#HelmChart & {
|
||||
values: installCrds: true
|
||||
namespace: #TargetNamespace
|
||||
chart: {
|
||||
name: "external-secrets"
|
||||
version: "0.9.12"
|
||||
repository: {
|
||||
name: "external-secrets"
|
||||
url: "https://charts.external-secrets.io"
|
||||
spec: components: HelmChartList: [
|
||||
#HelmChart & {
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
|
||||
metadata: name: "prod-secrets-eso"
|
||||
namespace: #TargetNamespace
|
||||
chart: {
|
||||
name: "external-secrets"
|
||||
version: "0.9.12"
|
||||
repository: {
|
||||
name: "external-secrets"
|
||||
url: "https://charts.external-secrets.io"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_values: installCrds: true
|
||||
},
|
||||
]
|
||||
|
||||
@@ -2,11 +2,3 @@ package holos
|
||||
|
||||
// Components under this directory are part of this collection
|
||||
#InputKeys: project: "secrets"
|
||||
|
||||
// Shared dependencies for all components in this collection.
|
||||
#DependsOn: _Namespaces
|
||||
|
||||
// Common Dependencies
|
||||
_Namespaces: Namespaces: name: "\(#StageName)-secrets-namespaces"
|
||||
_ESO: ESO: name: "\(#InstancePrefix)-eso"
|
||||
_ESOCreds: ESOCreds: name: "\(#InstancePrefix)-eso-creds-refresher"
|
||||
|
||||
@@ -2,27 +2,19 @@ package holos
|
||||
|
||||
import "list"
|
||||
|
||||
#DependsOn: _ESOCreds
|
||||
|
||||
#TargetNamespace: "default"
|
||||
|
||||
#InputKeys: {
|
||||
project: "secrets"
|
||||
component: "stores"
|
||||
}
|
||||
spec: components: KubernetesObjectsList: [
|
||||
#KubernetesObjects & {
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
_dependsOn: "prod-secrets-eso-creds-refresher": _
|
||||
|
||||
// #PlatformNamespaceObjects defines the api objects necessary for eso SecretStores in external clusters to access secrets in a given namespace in the provisioner cluster.
|
||||
#PlatformNamespaceObjects: {
|
||||
_ns: #PlatformNamespace
|
||||
metadata: name: "prod-secrets-stores"
|
||||
apiObjectMap: OBJECTS.apiObjectMap
|
||||
},
|
||||
]
|
||||
|
||||
objects: [
|
||||
#SecretStore & {
|
||||
_namespace: _ns.name
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
#KubernetesObjects & {
|
||||
let OBJECTS = #APIObjects & {
|
||||
apiObjects: {
|
||||
for ns in #PlatformNamespaces {
|
||||
for obj in (#PlatformNamespaceObjects & {_ns: ns}).objects {
|
||||
@@ -41,3 +33,14 @@ import "list"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// #PlatformNamespaceObjects defines the api objects necessary for eso SecretStores in external clusters to access secrets in a given namespace in the provisioner cluster.
|
||||
#PlatformNamespaceObjects: {
|
||||
_ns: #PlatformNamespace
|
||||
|
||||
objects: [
|
||||
#SecretStore & {
|
||||
_namespace: _ns.name
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -4,14 +4,16 @@ package holos
|
||||
|
||||
#TargetNamespace: "holos-system"
|
||||
|
||||
#InputKeys: {
|
||||
project: "secrets"
|
||||
component: "validate"
|
||||
}
|
||||
spec: components: KubernetesObjectsList: [
|
||||
#KubernetesObjects & {
|
||||
_dependsOn: "prod-secrets-stores": _
|
||||
|
||||
#DependsOn: _ESO
|
||||
metadata: name: "prod-secrets-validate"
|
||||
apiObjectMap: OBJECTS.apiObjectMap
|
||||
},
|
||||
]
|
||||
|
||||
#KubernetesObjects & {
|
||||
let OBJECTS = #APIObjects & {
|
||||
apiObjects: {
|
||||
ExternalSecret: validate: #ExternalSecret & {
|
||||
_name: "validate"
|
||||
|
||||
@@ -6,29 +6,30 @@ package holos
|
||||
|
||||
#SecretName: "\(#ClusterName)-ceph-csi-rbd"
|
||||
|
||||
#InputKeys: {
|
||||
project: "metal"
|
||||
service: "ceph"
|
||||
component: "ceph"
|
||||
}
|
||||
#Kustomization: spec: targetNamespace: "ceph-system"
|
||||
|
||||
#Kustomization: spec: targetNamespace: #TargetNamespace
|
||||
#DependsOn: Namespaces: name: "\(#StageName)-secrets-namespaces"
|
||||
spec: components: HelmChartList: [
|
||||
#HelmChart & {
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
|
||||
#HelmChart & {
|
||||
namespace: #TargetNamespace
|
||||
chart: {
|
||||
name: "ceph-csi-rbd"
|
||||
version: "3.10.2"
|
||||
repository: {
|
||||
name: "ceph-csi"
|
||||
url: "https://ceph.github.io/csi-charts"
|
||||
metadata: name: "prod-metal-ceph"
|
||||
|
||||
namespace: #TargetNamespace
|
||||
chart: {
|
||||
name: "ceph-csi-rbd"
|
||||
version: "3.10.2"
|
||||
repository: {
|
||||
name: "ceph-csi"
|
||||
url: "https://ceph.github.io/csi-charts"
|
||||
}
|
||||
}
|
||||
}
|
||||
_values: #ChartValues
|
||||
apiObjectMap: OBJECTS.apiObjectMap
|
||||
},
|
||||
]
|
||||
|
||||
let OBJECTS = #APIObjects & {
|
||||
apiObjects: {
|
||||
ExternalSecret: "\(#SecretName)": #ExternalSecret & {
|
||||
_name: #SecretName
|
||||
}
|
||||
ExternalSecret: "\(#SecretName)": _
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,46 +5,31 @@ import "encoding/yaml"
|
||||
import "list"
|
||||
|
||||
let Name = "vault"
|
||||
#InputKeys: component: Name
|
||||
#InputKeys: project: "core"
|
||||
#TargetNamespace: "\(#InstancePrefix)-\(Name)"
|
||||
#TargetNamespace: "prod-core-\(Name)"
|
||||
|
||||
let Vault = #OptionalServices[Name]
|
||||
|
||||
if Vault.enabled && list.Contains(Vault.clusterNames, #ClusterName) {
|
||||
#HelmChart & {
|
||||
namespace: #TargetNamespace
|
||||
chart: {
|
||||
name: Name
|
||||
version: "0.25.0"
|
||||
repository: {
|
||||
name: "hashicorp"
|
||||
url: "https://helm.releases.hashicorp.com"
|
||||
}
|
||||
}
|
||||
values: #Values
|
||||
#Kustomization: spec: wait: true
|
||||
|
||||
apiObjects: {
|
||||
ExternalSecret: "gcpkms-creds": _
|
||||
ExternalSecret: "vault-server-cert": _
|
||||
VirtualService: "\(Name)": {
|
||||
metadata: name: Name
|
||||
metadata: namespace: #TargetNamespace
|
||||
spec: hosts: [for cert in Vault.certs {cert.spec.commonName}]
|
||||
spec: gateways: ["istio-ingress/\(Name)"]
|
||||
spec: http: [
|
||||
{
|
||||
route: [
|
||||
{
|
||||
destination: host: "\(Name)-active"
|
||||
destination: port: number: 8200
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
if Vault.enabled && list.Contains(Vault.clusterNames, #ClusterName) {
|
||||
spec: components: HelmChartList: [
|
||||
#HelmChart & {
|
||||
metadata: name: "prod-core-\(Name)"
|
||||
|
||||
namespace: #TargetNamespace
|
||||
chart: {
|
||||
name: Name
|
||||
version: "0.25.0"
|
||||
repository: {
|
||||
name: "hashicorp"
|
||||
url: "https://helm.releases.hashicorp.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_values: #Values
|
||||
apiObjectMap: OBJECTS.apiObjectMap
|
||||
},
|
||||
|
||||
]
|
||||
|
||||
#Kustomize: {
|
||||
patches: [
|
||||
@@ -59,17 +44,40 @@ if Vault.enabled && list.Contains(Vault.clusterNames, #ClusterName) {
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
let EnvPatch = [
|
||||
{
|
||||
op: "test"
|
||||
path: "/spec/template/spec/containers/0/env/4/name"
|
||||
value: "VAULT_ADDR"
|
||||
},
|
||||
{
|
||||
op: "replace"
|
||||
path: "/spec/template/spec/containers/0/env/4/value"
|
||||
value: "http://$(VAULT_K8S_POD_NAME):8200"
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
let EnvPatch = [
|
||||
{
|
||||
op: "test"
|
||||
path: "/spec/template/spec/containers/0/env/4/name"
|
||||
value: "VAULT_ADDR"
|
||||
},
|
||||
{
|
||||
op: "replace"
|
||||
path: "/spec/template/spec/containers/0/env/4/value"
|
||||
value: "http://$(VAULT_K8S_POD_NAME):8200"
|
||||
},
|
||||
]
|
||||
|
||||
let OBJECTS = #APIObjects & {
|
||||
apiObjects: {
|
||||
ExternalSecret: "gcpkms-creds": _
|
||||
ExternalSecret: "vault-server-cert": _
|
||||
VirtualService: "\(Name)": {
|
||||
metadata: name: Name
|
||||
metadata: namespace: #TargetNamespace
|
||||
spec: hosts: [for cert in Vault.certs {cert.spec.commonName}]
|
||||
spec: gateways: ["istio-ingress/\(Name)"]
|
||||
spec: http: [
|
||||
{
|
||||
route: [
|
||||
{
|
||||
destination: host: "\(Name)-active"
|
||||
destination: port: number: 8200
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
52
docs/examples/platforms/reference/clusters/projects.cue
Normal file
52
docs/examples/platforms/reference/clusters/projects.cue
Normal file
@@ -0,0 +1,52 @@
|
||||
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: _
|
||||
clusters: k2: _
|
||||
stages: dev: authProxyClientID: "260505543108527218@holos"
|
||||
stages: prod: authProxyClientID: "260506079325128023@holos"
|
||||
environments: {
|
||||
prod: stage: "prod"
|
||||
dev: stage: "dev"
|
||||
jeff: stage: dev.stage
|
||||
gary: stage: dev.stage
|
||||
nate: stage: dev.stage
|
||||
}
|
||||
}
|
||||
|
||||
iam: {
|
||||
resourceId: 260582480954787159
|
||||
clusters: {
|
||||
core1: _
|
||||
core2: _
|
||||
}
|
||||
stages: dev: authProxyClientID: "260582521186616432@iam"
|
||||
stages: prod: authProxyClientID: "260582633862399090@iam"
|
||||
}
|
||||
}
|
||||
|
||||
// Manage namespaces for platform project environments.
|
||||
for project in _Projects {
|
||||
for ns in project.managedNamespaces {
|
||||
if ns.clusters[#ClusterName] != _|_ {
|
||||
#ManagedNamespaces: (ns.namespace.metadata.name): ns
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
package holos
|
||||
|
||||
#InputKeys: project: "projects"
|
||||
@@ -2,9 +2,3 @@ package holos
|
||||
|
||||
// Components under this directory are part of this collection
|
||||
#InputKeys: project: "iam"
|
||||
|
||||
// Shared dependencies for all components in this collection.
|
||||
#DependsOn: _Namespaces
|
||||
|
||||
// Common Dependencies
|
||||
_Namespaces: Namespaces: name: "\(#StageName)-secrets-namespaces"
|
||||
|
||||
@@ -8,13 +8,27 @@ 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"
|
||||
#InputKeys: component: "postgres-certs"
|
||||
|
||||
let SelfSigned = "\(_DBName)-selfsigned"
|
||||
let RootCA = "\(_DBName)-root-ca"
|
||||
let DBName = "zitadel"
|
||||
|
||||
let SelfSigned = "\(DBName)-selfsigned"
|
||||
let RootCA = "\(DBName)-root-ca"
|
||||
let Orgs = ["Database"]
|
||||
|
||||
#KubernetesObjects & {
|
||||
#Kustomization: spec: wait: true
|
||||
|
||||
spec: components: KubernetesObjectsList: [
|
||||
#KubernetesObjects & {
|
||||
metadata: name: "prod-iam-postgres-certs"
|
||||
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
apiObjectMap: OBJECTS.apiObjectMap
|
||||
},
|
||||
]
|
||||
|
||||
let OBJECTS = #APIObjects & {
|
||||
apiObjects: {
|
||||
// Put everything in the target namespace.
|
||||
[_]: {
|
||||
@@ -51,10 +65,10 @@ let Orgs = ["Database"]
|
||||
subject: organizations: Orgs
|
||||
}
|
||||
}
|
||||
"\(_DBName)-primary-tls": #DatabaseCert & {
|
||||
"\(DBName)-primary-tls": #DatabaseCert & {
|
||||
// PGO managed name is "<cluster name>-cluster-cert" e.g. zitadel-cluster-cert
|
||||
spec: {
|
||||
commonName: "\(_DBName)-primary"
|
||||
commonName: "\(DBName)-primary"
|
||||
dnsNames: [
|
||||
commonName,
|
||||
"\(commonName).\(#TargetNamespace)",
|
||||
@@ -66,16 +80,16 @@ let Orgs = ["Database"]
|
||||
usages: ["digital signature", "key encipherment"]
|
||||
}
|
||||
}
|
||||
"\(_DBName)-repl-tls": #DatabaseCert & {
|
||||
"\(DBName)-repl-tls": #DatabaseCert & {
|
||||
spec: {
|
||||
commonName: "_crunchyrepl"
|
||||
dnsNames: [commonName]
|
||||
usages: ["digital signature", "key encipherment"]
|
||||
}
|
||||
}
|
||||
"\(_DBName)-client-tls": #DatabaseCert & {
|
||||
"\(DBName)-client-tls": #DatabaseCert & {
|
||||
spec: {
|
||||
commonName: "\(_DBName)-client"
|
||||
commonName: "\(DBName)-client"
|
||||
dnsNames: [commonName]
|
||||
usages: ["digital signature", "key encipherment"]
|
||||
}
|
||||
|
||||
@@ -1,6 +1 @@
|
||||
package holos
|
||||
|
||||
#TargetNamespace: #InstancePrefix + "-zitadel"
|
||||
|
||||
// _DBName is the database name used across multiple holos components in this project
|
||||
_DBName: "zitadel"
|
||||
|
||||
@@ -1,20 +1,35 @@
|
||||
package holos
|
||||
|
||||
// Provision all platform certificates.
|
||||
#InputKeys: component: "certificates"
|
||||
|
||||
// Certificates usually go into the istio-system namespace, but they may go anywhere.
|
||||
#TargetNamespace: "default"
|
||||
|
||||
// Depends on issuers
|
||||
#DependsOn: _LetsEncrypt
|
||||
#Kustomization: spec: wait: true
|
||||
|
||||
#KubernetesObjects & {
|
||||
spec: components: KubernetesObjectsList: [
|
||||
#KubernetesObjects & {
|
||||
metadata: name: "\(#InstancePrefix)-certificates"
|
||||
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
_dependsOn: "prod-mesh-letsencrypt": _
|
||||
|
||||
apiObjectMap: OBJECTS.apiObjectMap
|
||||
},
|
||||
]
|
||||
|
||||
let Vault = #OptionalServices.vault
|
||||
|
||||
let OBJECTS = #APIObjects & {
|
||||
apiObjects: {
|
||||
for k, obj in #PlatformCerts {
|
||||
"\(obj.kind)": {
|
||||
"\(obj.metadata.namespace)/\(obj.metadata.name)": obj
|
||||
}
|
||||
}
|
||||
|
||||
if Vault.enabled {
|
||||
for k, obj in Vault.certs {
|
||||
"\(obj.kind)": "\(obj.metadata.name)": obj
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
package holos
|
||||
|
||||
let Vault = #OptionalServices.vault
|
||||
|
||||
if Vault.enabled {
|
||||
#KubernetesObjects & {
|
||||
apiObjects: {
|
||||
for k, obj in Vault.certs {
|
||||
"\(obj.kind)": "\(obj.metadata.name)": obj
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,28 +4,28 @@ package holos
|
||||
|
||||
#TargetNamespace: "cert-manager"
|
||||
|
||||
#InputKeys: {
|
||||
component: "certmanager"
|
||||
service: "cert-manager"
|
||||
}
|
||||
spec: components: HelmChartList: [
|
||||
#HelmChart & {
|
||||
metadata: name: "\(#InstancePrefix)-certmanager"
|
||||
|
||||
#HelmChart & {
|
||||
values: #Values & {
|
||||
installCRDs: true
|
||||
startupapicheck: enabled: false
|
||||
// Must not use kube-system on gke autopilot. GKE Warden authz blocks access.
|
||||
global: leaderElection: namespace: #TargetNamespace
|
||||
}
|
||||
namespace: #TargetNamespace
|
||||
chart: {
|
||||
name: "cert-manager"
|
||||
version: "1.14.3"
|
||||
repository: {
|
||||
name: "jetstack"
|
||||
url: "https://charts.jetstack.io"
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
namespace: #TargetNamespace
|
||||
_values: #Values & {
|
||||
installCRDs: true
|
||||
startupapicheck: enabled: false
|
||||
// Must not use kube-system on gke autopilot. GKE Warden authz blocks access.
|
||||
global: leaderElection: namespace: #TargetNamespace
|
||||
}
|
||||
}
|
||||
}
|
||||
chart: {
|
||||
name: "cert-manager"
|
||||
version: "1.14.3"
|
||||
repository: {
|
||||
name: "jetstack"
|
||||
url: "https://charts.jetstack.io"
|
||||
}
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
// https://cloud.google.com/kubernetes-engine/docs/concepts/autopilot-resource-requests#min-max-requests
|
||||
#PodResources: {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package holos
|
||||
|
||||
// Lets Encrypt certificate issuers for public tls certs
|
||||
#InputKeys: component: "letsencrypt"
|
||||
#TargetNamespace: "cert-manager"
|
||||
|
||||
let Name = "letsencrypt"
|
||||
@@ -9,10 +8,17 @@ let Name = "letsencrypt"
|
||||
// The cloudflare api token is platform scoped, not cluster scoped.
|
||||
#SecretName: "cloudflare-api-token-secret"
|
||||
|
||||
// Depends on cert manager
|
||||
#DependsOn: _CertManager
|
||||
spec: components: KubernetesObjectsList: [
|
||||
#KubernetesObjects & {
|
||||
metadata: name: "\(#InstancePrefix)-letsencrypt"
|
||||
|
||||
#KubernetesObjects & {
|
||||
_dependsOn: "prod-secrets-namespaces": _
|
||||
_dependsOn: "\(#InstancePrefix)-certmanager": _
|
||||
apiObjectMap: OBJECTS.apiObjectMap
|
||||
},
|
||||
]
|
||||
|
||||
let OBJECTS = #APIObjects & {
|
||||
apiObjects: {
|
||||
ClusterIssuer: {
|
||||
letsencrypt: #ClusterIssuer & {
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
package holos
|
||||
|
||||
// Components under this directory are part of this collection
|
||||
#InputKeys: project: "mesh"
|
||||
|
||||
// Shared dependencies for all components in this collection.
|
||||
#DependsOn: _Namespaces
|
||||
|
||||
// Common Dependencies
|
||||
_Namespaces: Namespaces: name: "\(#StageName)-secrets-namespaces"
|
||||
_CertManager: CertManager: name: "\(#InstancePrefix)-certmanager"
|
||||
_LetsEncrypt: LetsEncrypt: name: "\(#InstancePrefix)-letsencrypt"
|
||||
_Certificates: Certificates: name: "\(#InstancePrefix)-certificates"
|
||||
#InstancePrefix: "prod-mesh"
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package holos
|
||||
|
||||
for Project in _Projects {
|
||||
spec: components: resources: (#ProjectTemplate & {project: Project}).provisioner.resources
|
||||
}
|
||||
@@ -8,10 +8,15 @@ package holos
|
||||
// - Namespace
|
||||
// - ServiceAccount eso-reader, eso-writer
|
||||
|
||||
// No flux kustomization
|
||||
ksObjects: []
|
||||
spec: components: KubernetesObjectsList: [
|
||||
#KubernetesObjects & {
|
||||
metadata: name: "prod-secrets-eso-creds-refresher"
|
||||
|
||||
#KubernetesObjects & {
|
||||
apiObjectMap: OBJECTS.apiObjectMap
|
||||
},
|
||||
]
|
||||
|
||||
let OBJECTS = #APIObjects & {
|
||||
apiObjects: {
|
||||
let role = #CredsRefresherIAM.role
|
||||
let binding = #CredsRefresherIAM.binding
|
||||
@@ -35,12 +40,6 @@ ksObjects: []
|
||||
}
|
||||
}
|
||||
|
||||
#InputKeys: {
|
||||
cluster: "provisioner"
|
||||
project: "secrets"
|
||||
component: "eso-creds-refresher"
|
||||
}
|
||||
|
||||
// #CredsRefresherIAM defines the rbac policy for the job that refreshes credentials used by eso SecretStore resources in clusters other than the provisioner cluster.
|
||||
#CredsRefresherIAM: {
|
||||
let _name = #CredsRefresher.name
|
||||
|
||||
@@ -2,12 +2,15 @@ package holos
|
||||
|
||||
#TargetNamespace: "default"
|
||||
|
||||
#InputKeys: {
|
||||
project: "secrets"
|
||||
component: "namespaces"
|
||||
}
|
||||
spec: components: KubernetesObjectsList: [
|
||||
#KubernetesObjects & {
|
||||
metadata: name: "prod-secrets-namespaces"
|
||||
|
||||
#KubernetesObjects & {
|
||||
apiObjectMap: OBJECTS.apiObjectMap
|
||||
},
|
||||
]
|
||||
|
||||
let OBJECTS = #APIObjects & {
|
||||
apiObjects: {
|
||||
// #ManagedNamespaces is the set of all namespaces across all clusters in the platform.
|
||||
for nsName, ns in #ManagedNamespaces {
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package holos
|
||||
|
||||
for Project in _Projects {
|
||||
spec: components: resources: (#ProjectTemplate & {project: Project}).workload.resources
|
||||
}
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
531
docs/examples/platforms/reference/platform_projects.cue
Normal file
531
docs/examples/platforms/reference/platform_projects.cue
Normal file
@@ -0,0 +1,531 @@
|
||||
package holos
|
||||
|
||||
import "encoding/yaml"
|
||||
|
||||
// Platform level definition of a project.
|
||||
#Project: {
|
||||
name: string
|
||||
|
||||
// All projects have at least a prod environment and stage.
|
||||
stages: prod: stageSegments: []
|
||||
environments: prod: stage: "prod"
|
||||
environments: prod: envSegments: []
|
||||
stages: dev: _
|
||||
environments: dev: stage: "dev"
|
||||
environments: dev: envSegments: []
|
||||
// Ensure at least the project name is a short hostname. Additional may be added.
|
||||
hosts: (name): _
|
||||
|
||||
// environments share the stage segments of their stage.
|
||||
environments: [_]: {
|
||||
stage: string
|
||||
stageSegments: stages[stage].stageSegments
|
||||
}
|
||||
}
|
||||
|
||||
#ProjectTemplate: {
|
||||
project: #Project
|
||||
let Project = project
|
||||
|
||||
// GatewayServers maps Gateway spec.servers #GatewayServer values indexed by stage then name.
|
||||
let GatewayServers = {
|
||||
// Initialize all stages, even if they have no environments.
|
||||
for stage in project.stages {
|
||||
(stage.name): {}
|
||||
}
|
||||
|
||||
// For each stage, construct entries for the Gateway spec.servers.hosts field.
|
||||
for env in project.environments {
|
||||
(env.stage): {
|
||||
let Env = env
|
||||
let Stage = project.stages[env.stage]
|
||||
for host in (#EnvHosts & {project: Project, env: Env}).hosts {
|
||||
(host.name): #GatewayServer & {
|
||||
hosts: [
|
||||
"\(env.namespace)/\(host.name)",
|
||||
// Allow the authproxy VirtualService to match the project.authProxyPrefix path.
|
||||
"\(Stage.namespace)/\(host.name)",
|
||||
]
|
||||
port: host.port
|
||||
tls: credentialName: host.name
|
||||
tls: mode: "SIMPLE"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
workload: resources: {
|
||||
// Provide resources only if the project is managed on --cluster-name
|
||||
if project.clusters[#ClusterName] != _|_ {
|
||||
for stage in project.stages {
|
||||
let Stage = stage
|
||||
|
||||
// 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"
|
||||
}
|
||||
}).apiObjectMap
|
||||
}
|
||||
|
||||
// Manage auth-proxy in each stage
|
||||
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
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provisioner: resources: {
|
||||
for stage in project.stages {
|
||||
"\(stage.slug)-certs": #KubernetesObjects & {
|
||||
apiObjectMap: (#APIObjects & {
|
||||
for host in GatewayServers[stage.name] {
|
||||
let CN = host.tls.credentialName
|
||||
apiObjects: Certificate: (CN): #Certificate & {
|
||||
metadata: name: CN
|
||||
metadata: namespace: "istio-ingress"
|
||||
spec: {
|
||||
commonName: CN
|
||||
dnsNames: [CN]
|
||||
secretName: CN
|
||||
issuerRef: {
|
||||
kind: "ClusterIssuer"
|
||||
name: "letsencrypt"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}).apiObjectMap
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let HTTPBIN = {
|
||||
name: string | *"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 & {
|
||||
metadata: Metadata
|
||||
|
||||
spec: selector: matchLabels: Metadata.labels
|
||||
spec: template: {
|
||||
metadata: labels: Metadata.labels & #IstioSidecar & Labels
|
||||
spec: securityContext: seccompProfile: type: "RuntimeDefault"
|
||||
spec: containers: [{
|
||||
name: Name
|
||||
image: "quay.io/holos/mccutchen/go-httpbin"
|
||||
ports: [{containerPort: 8080}]
|
||||
securityContext: {
|
||||
seccompProfile: type: "RuntimeDefault"
|
||||
allowPrivilegeEscalation: false
|
||||
runAsNonRoot: true
|
||||
runAsUser: 8192
|
||||
runAsGroup: 8192
|
||||
capabilities: drop: ["ALL"]
|
||||
}}]
|
||||
}
|
||||
}
|
||||
Service: (Name): #Service & {
|
||||
metadata: Metadata
|
||||
spec: selector: Metadata.labels
|
||||
spec: ports: [
|
||||
{port: 80, targetPort: 8080, protocol: "TCP", name: "http"},
|
||||
]
|
||||
}
|
||||
VirtualService: (Name): #VirtualService & {
|
||||
metadata: Metadata
|
||||
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: http: [{route: [{destination: host: Name}]}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
let AUTHPROXY = {
|
||||
name: string | *"authproxy"
|
||||
project: #Project
|
||||
stage: #Stage
|
||||
servers: {}
|
||||
let Name = name
|
||||
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
|
||||
labels: {
|
||||
"app.kubernetes.io/name": name
|
||||
"app.kubernetes.io/instance": stage.name
|
||||
"app.kubernetes.io/part-of": stage.project
|
||||
}
|
||||
}
|
||||
|
||||
let RedisMetadata = {
|
||||
name: Name + "-redis"
|
||||
namespace: stage.namespace
|
||||
labels: {
|
||||
"app.kubernetes.io/name": name
|
||||
"app.kubernetes.io/instance": stage.name
|
||||
"app.kubernetes.io/part-of": stage.project
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
// project.dev.example.com, project.dev.k1.example.com, project.dev.k2.example.com
|
||||
let StageDomains = {
|
||||
for host in (#StageDomains & {project: Project, stage: Stage}).hosts {
|
||||
(host.name): host
|
||||
}
|
||||
}
|
||||
|
||||
spec: {
|
||||
replicas: 1
|
||||
selector: matchLabels: Metadata.labels
|
||||
template: {
|
||||
metadata: labels: Metadata.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-\(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"]
|
||||
}
|
||||
}]
|
||||
volumes: [{name: "config", configMap: name: Name}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Service: (Name): #Service & {
|
||||
metadata: Metadata
|
||||
spec: selector: Metadata.labels
|
||||
spec: ports: [
|
||||
{port: 4180, targetPort: 4180, protocol: "TCP", name: "http"},
|
||||
]
|
||||
}
|
||||
VirtualService: (Name): #VirtualService & {
|
||||
metadata: Metadata
|
||||
spec: hosts: ["*"]
|
||||
spec: gateways: ["istio-ingress/\(stage.slug)"]
|
||||
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
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package holos
|
||||
@@ -1,11 +1,200 @@
|
||||
package holos
|
||||
|
||||
import h "github.com/holos-run/holos/api/v1alpha1"
|
||||
|
||||
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
|
||||
resourceId: number
|
||||
let ProjectName = name
|
||||
description: string
|
||||
environments: [Name=string]: #Environment & {
|
||||
name: Name
|
||||
project: ProjectName
|
||||
}
|
||||
stages: [Name=string]: #Stage & {
|
||||
name: Name
|
||||
project: ProjectName
|
||||
}
|
||||
domain: string | *#Platform.org.domain
|
||||
|
||||
// authProxyOrgDomain is the primary org domain for zitadel.
|
||||
authProxyOrgDomain: string | *#Platform.org.domain
|
||||
// authProxyIssuer is the issuer url
|
||||
authProxyIssuer: string | *"https://login.\(#Platform.org.domain)"
|
||||
|
||||
// hosts are short hostnames to configure for the project.
|
||||
// Each value is routed to every environment in the project as a dns prefix.
|
||||
hosts: [Name=string]: #Host & {name: Name}
|
||||
// clusters are the cluster names the project is configured on.
|
||||
clusters: [Name=string]: #Cluster & {name: Name}
|
||||
clusterNames: [for c in clusters {c.name}]
|
||||
|
||||
// managedNamespaces ensures project namespaces have SecretStores that can sync ExternalSecrets from the provisioner cluster.
|
||||
managedNamespaces: {
|
||||
// Define the shape of a managed namespace.
|
||||
[Name=_]: #ManagedNamespace & {
|
||||
namespace: metadata: name: Name
|
||||
clusterNames: ["provisioner", for c in clusters {c.name}]
|
||||
}
|
||||
|
||||
// Manage a system namespace for each stage in the project.
|
||||
for stage in stages {
|
||||
for ns in stage.namespaces {
|
||||
(ns.name): _
|
||||
}
|
||||
}
|
||||
|
||||
// Manage a namespace for each environment in the project.
|
||||
for env in environments {
|
||||
(env.namespace): _
|
||||
}
|
||||
}
|
||||
|
||||
// features is YAGNI maybe?
|
||||
features: [Name=string]: #Feature & {name: Name}
|
||||
features: authproxy: _
|
||||
features: httpbin: _
|
||||
}
|
||||
|
||||
#Projects: {
|
||||
[Name=_]: #Project & {
|
||||
name: Name
|
||||
// #Cluster defines a cluster
|
||||
#Cluster: name: string
|
||||
|
||||
// #Host defines a short hostname
|
||||
#Host: name: string
|
||||
|
||||
#Environment: {
|
||||
// name uniquely identifies the environment within the scope of the project.
|
||||
name: string
|
||||
project: string
|
||||
stage: string | "dev" | "prod"
|
||||
slug: "\(name)-\(project)"
|
||||
namespace: "\(name)-\(project)"
|
||||
stageSlug: "\(stage)-\(project)"
|
||||
|
||||
// envSegments are the env portion of the dns segments
|
||||
envSegments: [...string] | *[name]
|
||||
// stageSegments are the stage portion of the dns segments
|
||||
stageSegments: [...string] | *[stage]
|
||||
|
||||
// #host provides a hostname
|
||||
// Refer to: https://github.com/holos-run/holos/issues/66#issuecomment-2027562626
|
||||
#host: {
|
||||
name: string
|
||||
cluster?: string
|
||||
clusterSegments: [...string]
|
||||
if cluster != _|_ {
|
||||
clusterSegments: [cluster]
|
||||
}
|
||||
let SEGMENTS = envSegments + [name] + stageSegments + clusterSegments + [#Platform.org.domain]
|
||||
let NAMESEGMENTS = ["https"] + SEGMENTS
|
||||
host: {
|
||||
name: strings.Join(SEGMENTS, ".")
|
||||
port: {
|
||||
name: strings.Replace(strings.Join(NAMESEGMENTS, "-"), ".", "-", -1)
|
||||
number: 443
|
||||
protocol: "HTTPS"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Stage: {
|
||||
name: string
|
||||
project: string
|
||||
slug: "\(name)-\(project)"
|
||||
// namespace is the system namespace for the project stage
|
||||
namespace: "\(name)-\(project)-system"
|
||||
// Manage a system namespace for each stage
|
||||
namespaces: [Name=_]: name: Name
|
||||
namespaces: (namespace): _
|
||||
// stageSegments are the stage portion of the dns segments
|
||||
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
|
||||
}
|
||||
|
||||
#ProjectTemplate: {
|
||||
project: #Project
|
||||
|
||||
// workload cluster resources
|
||||
workload: resources: [Name=_]: h.#KubernetesObjects & {
|
||||
metadata: name: Name
|
||||
}
|
||||
|
||||
// provisioner cluster resources
|
||||
provisioner: resources: [Name=_]: h.#KubernetesObjects & {
|
||||
metadata: name: Name
|
||||
}
|
||||
}
|
||||
|
||||
// #EnvHosts provides hostnames given a project and environment.
|
||||
// Refer to https://github.com/holos-run/holos/issues/66#issuecomment-2027562626
|
||||
#EnvHosts: {
|
||||
project: #Project & {name: env.project}
|
||||
env: #Environment
|
||||
|
||||
hosts: {
|
||||
for host in project.hosts {
|
||||
// globally scoped hostname
|
||||
let HOST = (env.#host & {name: host.name}).host
|
||||
(HOST.name): HOST
|
||||
|
||||
// cluster scoped hostname
|
||||
for Cluster in project.clusters {
|
||||
let HOST = (env.#host & {name: host.name, cluster: Cluster.name}).host
|
||||
(HOST.name): HOST
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// #StageDomains provides hostnames given a project and stage. Primarily for the
|
||||
// auth proxy.
|
||||
// Refer to https://github.com/holos-run/holos/issues/66#issuecomment-2027562626
|
||||
#StageDomains: {
|
||||
// names are the leading prefix names to create hostnames for.
|
||||
// this is a two level list to support strings.Join()
|
||||
prefixes: [...[...string]] | *[[]]
|
||||
stage: #Stage
|
||||
project: #Project & {
|
||||
name: stage.project
|
||||
}
|
||||
|
||||
// blank segment for the global domain plus each cluster in the project.
|
||||
let ClusterSegments = [[], for cluster in project.clusters {[cluster.name]}]
|
||||
|
||||
hosts: {
|
||||
for prefix in prefixes {
|
||||
for ClusterSegment in ClusterSegments {
|
||||
let SEGMENTS = prefix + [project.name] + stage.stageSegments + ClusterSegment + [project.domain]
|
||||
let NAMESEGMENTS = ["https"] + SEGMENTS
|
||||
let HOSTNAME = strings.Join(SEGMENTS, ".")
|
||||
(HOSTNAME): {
|
||||
name: HOSTNAME
|
||||
port: {
|
||||
name: strings.Replace(strings.Join(NAMESEGMENTS, "-"), ".", "-", -1)
|
||||
number: 443
|
||||
protocol: "HTTPS"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -54,26 +56,35 @@ _apiVersion: "holos.run/v1alpha1"
|
||||
}
|
||||
|
||||
// Kubernetes API Objects
|
||||
#Namespace: corev1.#Namespace
|
||||
#Namespace: corev1.#Namespace & {
|
||||
metadata: name: string
|
||||
metadata: labels: "kubernetes.io/metadata.name": metadata.name
|
||||
}
|
||||
|
||||
#ClusterRole: #ClusterObject & rbacv1.#ClusterRole
|
||||
#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
|
||||
#Gateway: #NamespaceObject & gw.#Gateway
|
||||
#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"
|
||||
spec: selector: istio: string | *"ingressgateway"
|
||||
}
|
||||
|
||||
// #HTTP01Cert defines a http01 certificate.
|
||||
#HTTP01Cert: {
|
||||
@@ -101,7 +112,7 @@ _apiVersion: "holos.run/v1alpha1"
|
||||
_name: string
|
||||
metadata: {
|
||||
name: _name
|
||||
namespace: #TargetNamespace
|
||||
namespace: string | *#TargetNamespace
|
||||
}
|
||||
spec: {
|
||||
refreshInterval: string | *"1h"
|
||||
@@ -214,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.
|
||||
@@ -226,6 +263,9 @@ _apiVersion: "holos.run/v1alpha1"
|
||||
}
|
||||
// clusterNames represents the set of clusters the namespace is managed on. Usually all clusters.
|
||||
clusterNames: [...string]
|
||||
for cluster in clusterNames {
|
||||
clusters: (cluster): name: cluster
|
||||
}
|
||||
}
|
||||
|
||||
// #ManagedNamepsaces is the union of all namespaces across all cluster types and optional services.
|
||||
@@ -292,3 +332,77 @@ _apiVersion: "holos.run/v1alpha1"
|
||||
// #IsPrimaryCluster is true if the cluster being rendered is the primary cluster
|
||||
// Used by the iam project to determine where https://login.example.com is active.
|
||||
#IsPrimaryCluster: bool & #ClusterName == #Platform.primaryCluster.name
|
||||
|
||||
// #GatewayServer defines the value of the istio Gateway.spec.servers field.
|
||||
#GatewayServer: {
|
||||
// The ip or the Unix domain socket to which the listener should
|
||||
// be bound to.
|
||||
bind?: string
|
||||
defaultEndpoint?: string
|
||||
|
||||
// One or more hosts exposed by this gateway.
|
||||
hosts: [...string]
|
||||
|
||||
// An optional name of the server, when set must be unique across
|
||||
// all servers.
|
||||
name?: string
|
||||
|
||||
// The Port on which the proxy should listen for incoming
|
||||
// connections.
|
||||
port: {
|
||||
// Label assigned to the port.
|
||||
name: string
|
||||
|
||||
// A valid non-negative integer port number.
|
||||
number: int
|
||||
|
||||
// The protocol exposed on the port.
|
||||
protocol: string
|
||||
targetPort?: int
|
||||
}
|
||||
|
||||
// Set of TLS related options that govern the server's behavior.
|
||||
tls?: {
|
||||
// REQUIRED if mode is `MUTUAL` or `OPTIONAL_MUTUAL`.
|
||||
caCertificates?: string
|
||||
|
||||
// Optional: If specified, only support the specified cipher list.
|
||||
cipherSuites?: [...string]
|
||||
|
||||
// For gateways running on Kubernetes, the name of the secret that
|
||||
// holds the TLS certs including the CA certificates.
|
||||
credentialName?: string
|
||||
|
||||
// If set to true, the load balancer will send a 301 redirect for
|
||||
// all http connections, asking the clients to use HTTPS.
|
||||
httpsRedirect?: bool
|
||||
|
||||
// Optional: Maximum TLS protocol version.
|
||||
maxProtocolVersion?: "TLS_AUTO" | "TLSV1_0" | "TLSV1_1" | "TLSV1_2" | "TLSV1_3"
|
||||
|
||||
// Optional: Minimum TLS protocol version.
|
||||
minProtocolVersion?: "TLS_AUTO" | "TLSV1_0" | "TLSV1_1" | "TLSV1_2" | "TLSV1_3"
|
||||
|
||||
// Optional: Indicates whether connections to this port should be
|
||||
// secured using TLS.
|
||||
mode?: "PASSTHROUGH" | "SIMPLE" | "MUTUAL" | "AUTO_PASSTHROUGH" | "ISTIO_MUTUAL" | "OPTIONAL_MUTUAL"
|
||||
|
||||
// REQUIRED if mode is `SIMPLE` or `MUTUAL`.
|
||||
privateKey?: string
|
||||
|
||||
// REQUIRED if mode is `SIMPLE` or `MUTUAL`.
|
||||
serverCertificate?: string
|
||||
|
||||
// A list of alternate names to verify the subject identity in the
|
||||
// certificate presented by the client.
|
||||
subjectAltNames?: [...string]
|
||||
|
||||
// An optional list of hex-encoded SHA-256 hashes of the
|
||||
// authorized client certificates.
|
||||
verifyCertificateHash?: [...string]
|
||||
|
||||
// An optional list of base64-encoded SHA-256 hashes of the SPKIs
|
||||
// of authorized client certificates.
|
||||
verifyCertificateSpki?: [...string]
|
||||
}
|
||||
}
|
||||
|
||||
4
holos.go
4
holos.go
@@ -5,6 +5,6 @@ package holos
|
||||
// It is given a unique type so the API is clear.
|
||||
type PathCueMod string
|
||||
|
||||
// A PathComponent is a string representing the filesystem path of a holos component.
|
||||
// A InstancePath is a string representing the filesystem path of a holos instance.
|
||||
// It is given a unique type so the API is clear.
|
||||
type PathComponent string
|
||||
type InstancePath string
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -129,13 +131,22 @@ func (b *Builder) Run(ctx context.Context) (results []*v1alpha1.Result, err erro
|
||||
if err := value.Validate(); err != nil {
|
||||
return nil, wrapper.Wrap(fmt.Errorf("could not validate: %w", err))
|
||||
}
|
||||
|
||||
log.DebugContext(ctx, "cue: decoding holos build plan")
|
||||
if err := value.Decode(&buildPlan); err != nil {
|
||||
return nil, wrapper.Wrap(fmt.Errorf("could not decode: %w", err))
|
||||
// Hack to catch unknown fields https://github.com/holos-run/holos/issues/72
|
||||
jsonBytes, err := value.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, wrapper.Wrap(fmt.Errorf("could not marshal cue instance %s: %w", instance.Dir, err))
|
||||
}
|
||||
decoder := json.NewDecoder(bytes.NewReader(jsonBytes))
|
||||
decoder.DisallowUnknownFields()
|
||||
err = decoder.Decode(&buildPlan)
|
||||
if err != nil {
|
||||
return nil, wrapper.Wrap(fmt.Errorf("invalid BuildPlan: %s: %w", instance.Dir, err))
|
||||
}
|
||||
|
||||
if err := buildPlan.Validate(); err != nil {
|
||||
return nil, wrapper.Wrap(fmt.Errorf("could not render: %w", err))
|
||||
return nil, wrapper.Wrap(fmt.Errorf("could not validate %s: %w", instance.Dir, err))
|
||||
}
|
||||
|
||||
if buildPlan.Spec.Disabled {
|
||||
@@ -143,22 +154,30 @@ func (b *Builder) Run(ctx context.Context) (results []*v1alpha1.Result, err erro
|
||||
continue
|
||||
}
|
||||
|
||||
for _, component := range buildPlan.Spec.Components.KubernetesObjects {
|
||||
if result, err := component.Render(ctx, holos.PathComponent(instance.Dir)); err != nil {
|
||||
// TODO: concurrent renders
|
||||
for _, component := range buildPlan.Spec.Components.Resources {
|
||||
if result, err := component.Render(ctx, holos.InstancePath(instance.Dir)); err != nil {
|
||||
return nil, wrapper.Wrap(fmt.Errorf("could not render: %w", err))
|
||||
} else {
|
||||
results = append(results, result)
|
||||
}
|
||||
}
|
||||
for _, component := range buildPlan.Spec.Components.HelmCharts {
|
||||
if result, err := component.Render(ctx, holos.PathComponent(instance.Dir)); err != nil {
|
||||
for _, component := range buildPlan.Spec.Components.KubernetesObjectsList {
|
||||
if result, err := component.Render(ctx, holos.InstancePath(instance.Dir)); err != nil {
|
||||
return nil, wrapper.Wrap(fmt.Errorf("could not render: %w", err))
|
||||
} else {
|
||||
results = append(results, result)
|
||||
}
|
||||
}
|
||||
for _, component := range buildPlan.Spec.Components.KustomizeBuilds {
|
||||
if result, err := component.Render(ctx, holos.PathComponent(instance.Dir)); err != nil {
|
||||
for _, component := range buildPlan.Spec.Components.HelmChartList {
|
||||
if result, err := component.Render(ctx, holos.InstancePath(instance.Dir)); err != nil {
|
||||
return nil, wrapper.Wrap(fmt.Errorf("could not render: %w", err))
|
||||
} else {
|
||||
results = append(results, result)
|
||||
}
|
||||
}
|
||||
for _, component := range buildPlan.Spec.Components.KustomizeBuildList {
|
||||
if result, err := component.Render(ctx, holos.InstancePath(instance.Dir)); err != nil {
|
||||
return nil, wrapper.Wrap(fmt.Errorf("could not render: %w", err))
|
||||
} else {
|
||||
results = append(results, result)
|
||||
|
||||
@@ -1 +1 @@
|
||||
60
|
||||
61
|
||||
|
||||
@@ -1 +1 @@
|
||||
1
|
||||
7
|
||||
|
||||
Reference in New Issue
Block a user