219 lines
6.8 KiB
Go
219 lines
6.8 KiB
Go
|
|
package main
|
||
|
|
|
||
|
|
import (
|
||
|
|
"bytes"
|
||
|
|
"encoding/json"
|
||
|
|
"io"
|
||
|
|
"slices"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
"text/template"
|
||
|
|
|
||
|
|
"gopkg.in/yaml.v3"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestAccountStaticFrontend(t *testing.T) {
|
||
|
|
deployment := serviceDeployment(t, "k8s/normal/server/deploy.yaml")
|
||
|
|
for _, container := range deployment.Spec.Template.Spec.Containers {
|
||
|
|
if container.Name != "account-web" {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
prefix, digest, pinned := strings.Cut(container.Image, "@sha256:")
|
||
|
|
if !pinned || prefix != "registry.quorate.internal/quorate/mail-keys" || !hexValue(digest, 64) {
|
||
|
|
t.Fatal("account frontend does not pin the custom UI image")
|
||
|
|
}
|
||
|
|
if len(container.Command) != 0 || len(container.Args) != 0 {
|
||
|
|
t.Fatal("account frontend overrides the image entrypoint")
|
||
|
|
}
|
||
|
|
for _, mount := range container.VolumeMounts {
|
||
|
|
if mount.MountPath == "/etc/nginx/nginx.conf" {
|
||
|
|
t.Fatal("account frontend overrides the static UI routing")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return
|
||
|
|
}
|
||
|
|
t.Fatal("account frontend is absent")
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestAdminAPIKeepsIngress(t *testing.T) {
|
||
|
|
var route struct {
|
||
|
|
Spec struct {
|
||
|
|
Hostnames []string `yaml:"hostnames"`
|
||
|
|
Rules []struct {
|
||
|
|
BackendRefs []struct {
|
||
|
|
Name string `yaml:"name"`
|
||
|
|
Port int `yaml:"port"`
|
||
|
|
} `yaml:"backendRefs"`
|
||
|
|
} `yaml:"rules"`
|
||
|
|
} `yaml:"spec"`
|
||
|
|
}
|
||
|
|
if err := yaml.Unmarshal(serviceFiles(t, "k8s/normal/gateway/admin.yaml"), &route); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if !slices.Equal(route.Spec.Hostnames, []string{"mail.quorate.internal"}) || len(route.Spec.Rules) != 1 {
|
||
|
|
t.Fatal("internal administration route is absent")
|
||
|
|
}
|
||
|
|
backends := route.Spec.Rules[0].BackendRefs
|
||
|
|
if len(backends) != 1 || backends[0].Name != "stalwart-admin" || backends[0].Port != 8080 {
|
||
|
|
t.Fatal("internal administration route does not target the management API")
|
||
|
|
}
|
||
|
|
var operations []struct {
|
||
|
|
Object string `json:"object"`
|
||
|
|
Value json.RawMessage `json:"value"`
|
||
|
|
}
|
||
|
|
if err := json.Unmarshal(serviceFiles(t, "k8s/base/plan.json"), &operations); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
for _, operation := range operations {
|
||
|
|
if operation.Object != "Application" {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
var apps map[string]struct {
|
||
|
|
Enabled bool `json:"enabled"`
|
||
|
|
}
|
||
|
|
if err := json.Unmarshal(operation.Value, &apps); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if app, present := apps["bundled-webui"]; !present || len(apps) != 1 || app.Enabled {
|
||
|
|
t.Fatal("built-in WebUI default is not explicitly disabled")
|
||
|
|
}
|
||
|
|
return
|
||
|
|
}
|
||
|
|
t.Fatal("account application declaration is absent")
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestAccountPublicConfig(t *testing.T) {
|
||
|
|
decoder := yaml.NewDecoder(bytes.NewReader(serviceFiles(t, "k8s/base/vault/identity.yaml")))
|
||
|
|
for {
|
||
|
|
var secret struct {
|
||
|
|
Metadata struct {
|
||
|
|
Name string `yaml:"name"`
|
||
|
|
} `yaml:"metadata"`
|
||
|
|
Spec struct {
|
||
|
|
Destination struct {
|
||
|
|
Transformation struct {
|
||
|
|
ExcludeRaw bool `yaml:"excludeRaw"`
|
||
|
|
Excludes []string `yaml:"excludes"`
|
||
|
|
Templates map[string]struct {
|
||
|
|
Text string `yaml:"text"`
|
||
|
|
} `yaml:"templates"`
|
||
|
|
} `yaml:"transformation"`
|
||
|
|
} `yaml:"destination"`
|
||
|
|
} `yaml:"spec"`
|
||
|
|
}
|
||
|
|
if err := decoder.Decode(&secret); err == io.EOF {
|
||
|
|
t.Fatal("public account config projection is absent")
|
||
|
|
} else if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if secret.Metadata.Name != "mail-web" {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
transform := secret.Spec.Destination.Transformation
|
||
|
|
if !transform.ExcludeRaw || len(transform.Excludes) != 1 || transform.Excludes[0] != ".*" || len(transform.Templates) != 1 {
|
||
|
|
t.Fatal("account config projection includes raw identity metadata")
|
||
|
|
}
|
||
|
|
functions := template.FuncMap{
|
||
|
|
"get": func(values map[string]string, key string) string { return values[key] },
|
||
|
|
"dict": func(values ...string) map[string]string {
|
||
|
|
result := make(map[string]string)
|
||
|
|
for index := 0; index < len(values); index += 2 {
|
||
|
|
result[values[index]] = values[index+1]
|
||
|
|
}
|
||
|
|
return result
|
||
|
|
},
|
||
|
|
"toJson": func(value any) (string, error) {
|
||
|
|
encoded, err := json.Marshal(value)
|
||
|
|
return string(encoded), err
|
||
|
|
},
|
||
|
|
}
|
||
|
|
parsed, err := template.New("config").Funcs(functions).Parse(transform.Templates["config.json"].Text)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
var output bytes.Buffer
|
||
|
|
if err := parsed.Execute(&output, map[string]any{"Secrets": map[string]string{
|
||
|
|
"issuer_url": "https://identity.example.invalid", "account_client_id": "synthetic-account",
|
||
|
|
"audience": "synthetic-project", "client_secret": "synthetic-machine-secret",
|
||
|
|
}}); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
var config map[string]string
|
||
|
|
if err := json.Unmarshal(output.Bytes(), &config); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if len(config) != 3 || config["issuer"] != "https://identity.example.invalid" || config["clientId"] != "synthetic-account" || config["scope"] != "openid profile urn:zitadel:iam:org:project:id:synthetic-project:aud" {
|
||
|
|
t.Fatalf("public account config = %#v", config)
|
||
|
|
}
|
||
|
|
break
|
||
|
|
}
|
||
|
|
var deployment struct {
|
||
|
|
Spec struct {
|
||
|
|
Template struct {
|
||
|
|
Spec struct {
|
||
|
|
Volumes []struct {
|
||
|
|
Name string `yaml:"name"`
|
||
|
|
Secret *struct {
|
||
|
|
SecretName string `yaml:"secretName"`
|
||
|
|
Items []struct {
|
||
|
|
Key string `yaml:"key"`
|
||
|
|
Path string `yaml:"path"`
|
||
|
|
} `yaml:"items"`
|
||
|
|
} `yaml:"secret"`
|
||
|
|
} `yaml:"volumes"`
|
||
|
|
Containers []struct {
|
||
|
|
Name string `yaml:"name"`
|
||
|
|
VolumeMounts []struct {
|
||
|
|
Name string `yaml:"name"`
|
||
|
|
MountPath string `yaml:"mountPath"`
|
||
|
|
SubPath string `yaml:"subPath"`
|
||
|
|
ReadOnly bool `yaml:"readOnly"`
|
||
|
|
} `yaml:"volumeMounts"`
|
||
|
|
} `yaml:"containers"`
|
||
|
|
} `yaml:"spec"`
|
||
|
|
} `yaml:"template"`
|
||
|
|
} `yaml:"spec"`
|
||
|
|
}
|
||
|
|
if err := yaml.Unmarshal(serviceFiles(t, "k8s/normal/server/deploy.yaml"), &deployment); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
secrets := make(map[string]string)
|
||
|
|
configFound := false
|
||
|
|
for _, volume := range deployment.Spec.Template.Spec.Volumes {
|
||
|
|
if volume.Secret == nil {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
secret := volume.Secret
|
||
|
|
secrets[volume.Name] = secret.SecretName
|
||
|
|
if volume.Name != "account-config" {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
configFound = true
|
||
|
|
if secret.SecretName != "mail-web" || len(secret.Items) != 1 || secret.Items[0].Key != "config.json" || secret.Items[0].Path != "config.json" {
|
||
|
|
t.Fatal("account config volume projects identity material beyond its public config")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if !configFound {
|
||
|
|
t.Fatal("account config volume is absent")
|
||
|
|
}
|
||
|
|
for _, container := range deployment.Spec.Template.Spec.Containers {
|
||
|
|
if container.Name != "account-web" {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
mounted := false
|
||
|
|
for _, mount := range container.VolumeMounts {
|
||
|
|
secret := secrets[mount.Name]
|
||
|
|
if secret != "" && secret != "mail-web" && secret != "stalwart-tls" {
|
||
|
|
t.Fatal("account frontend mounts an automation identity")
|
||
|
|
}
|
||
|
|
if mount.Name == "account-config" {
|
||
|
|
mounted = mount.MountPath == "/run/config" && mount.SubPath == "" && mount.ReadOnly
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if !mounted {
|
||
|
|
t.Fatal("account frontend lacks a read-only public config projection")
|
||
|
|
}
|
||
|
|
return
|
||
|
|
}
|
||
|
|
t.Fatal("account frontend is absent")
|
||
|
|
}
|