ce7cb702218693d08ea6d83464d2457c9250027c
This commit is contained in:
commit
34c5f8963c
1 changed files with 145336 additions and 0 deletions
219
services/stalwart/cmd/controller/public_test.go
Normal file
219
services/stalwart/cmd/controller/public_test.go
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
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")
|
||||
}
|
||||
Loading…
Reference in a new issue